The Bottom of a Graph

题目:

Description

We will use the following (standard) definitions from graph theory. Let V be a nonempty and finite set, its elements being called vertices (or nodes). Let E be a subset of the Cartesian product V x V, its elements being called edges. Then G=(V,E) is called a directed graph. 
Let n be a positive integer, and let p=(e, ... , e) be a sequence of length n of edges ei  E such that ei = (vi ,vi+1 ) for a sequence of vertices ( v,... , vn+1 ). Then p is called a path from vertex v1 to vertex vn+1 in G and we say that vn+1 is reachable from v1, writing ( v vn+1 )
Here are some new definitions. A node v in a graph G=( V, E ) is called a sink, if for every node w in G that is reachable from vv is also reachable from w. The bottom of a graph is the subset of all nodes that are sinks, i.e., bottom(G) = { vV | wV: (vw)  (wv) }. You have to calculate the bottom of certain graphs.

Input

The input contains several test cases, each of which corresponds to a directed graph G. Each test case starts with an integer number v, denoting the number of vertices of G=( V, E ), where the vertices will be identified by the integer numbers in the set V={ 1 , ... , v }. You may assume that 1 <= v <= 5000. That is followed by a non-negative integer e and, thereafter, e pairs of vertex identifiers v, w, ... , v, we with the meaning that (vi , wi  )  E. There are no edges other than specified by these pairs. The last test case is followed by a zero.

Output

For each test case output the bottom of the specified graph on a single line. To this end, print the numbers of all nodes that are sinks in sorted order separated by a single space character. If the bottom is empty, print an empty line.

Sample Input

3 3

1 3 2 3 3 1

2 1

1 2

0

Sample Output

1 3
2

 

 

这题就是求他的强连通!先来介绍一下

Tarjan算法!这个算法!是基于一次dfs操作每个节点有且仅有一次访问的机会!他利用到栈!在dfs每个节点的时候如果没访问到就访问,入栈!如果不符合上述的条件就退栈!(怎么那么像走迷宫的?其实不是啊!相似而已,莫激动)!

在这里我们用到low[n],dfs[n],这两个数组!dfs(u)为节点u搜索的次序编号(时间戳),low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。

算法伪代码如下

tarjan(u) 
{

    DFN[u]=Low[u]=++Index     // 为节点u设定次序编号和Low初值

    Stack.push(u)                     // 将节点u压入栈中

    for each (u, v) in E               // 枚举每一条边

          if (v is not visted)          // 如果节点v未被访问过

                  tarjan(v)              // 继续向下找

                  Low[u] = min(Low[u], Low[v])

            else if (v in S)            // 如果节点v还在栈内

            Low[u] = min(Low[u], DFN[v])

    if (DFN[u] == Low[u])        // 如果节点u是强连通分量的根

       repeat

           v = S.pop                  // 将v退栈,为该强连通分量中一个顶点

           print v

      until (u== v)

}

说真的我还不是很明白为何要缩点?缩点我也不怎么会,今天就看缩点啦!

下面就是代码ac:

#include"stdio.h"

struct node

{

       int v,next;

}edg[5005*5005];

int g[500005],vist[500005],low[500005],stack[500005],f[500005];

int top,id,flag[500005],dfn[500005];

#define min(x,y) x<y?x:y

int dfs(int u,int t)

{

       int i;vist[u]=1;stack[top++]=u;

       dfn[u]=low[u]=t;

       for(i=g[u];i!=-1;i=edg[i].next)

       {

              if(!vist[edg[i].v])

              {

                     t=dfs(edg[i].v,t+1);

                     low[u]=min(low[u],low[edg[i].v]);//如果有孩子因为后向边被缩小了low值,则它也相应缩小low值,整个分量的low都取分量中low最小的值。

              }

              else if(!f[edg[i].v])//如果f[v]还是0,表示u->v是后向边,v还没出栈

                     low[u]=min(low[u],low[edg[i].v]);

       }

       if(dfn[u]==low[u])

       {

              id++;

              do{

                     f[stack[--top]]=id;

              }while(top>0&&stack[top]!=u);

       }

       return t;

}

void tarjan(int n)

{

       int t=0,i;i=0;

       for(i=1;i<=n;i++)

       {

              if(!vist[i])//如果i点能与其它点x构成一个分量,则一定能搜到x,否则,它自己便是一个分量,所以这里的搜索不会重复。

                     t=dfs(i,t+1);

       }

}

void init(int n)

{

       for(int i=1;i<=n;i++)

              g[i]=-1,vist[i]=0,f[i]=0,flag[i]=0;

}

int main()

{

       int n,m,i,j,k,x,y;

       while(scanf("%d",&n)&&n)

       {

              init(n);

              scanf("%d",&m);k=0;

              for(i=0;i<m;i++)

              {

                     scanf("%d%d",&x,&y);

                     edg[k].v=y;edg[k].next=g[x];g[x]=k++;

              }

              tarjan(n);

              for(i=1;i<=n;i++)

              {

                     for(j=g[i];j!=-1;j=edg[j].next)

                            if(f[edg[j].v]!=f[i])

                            {

                                   flag[f[i]]=1;

                                   break;

                            }

              }

              for(i=1;i<=n;i++)

              {

                     if(!flag[f[i]])

                            printf("%d ",i);

              }

              printf("\n");

       }

       return 0;

}

求强连通还有一种

Kosaraju算法:这个算法是基于两次的dfs操作的!

1:dfs图并记录节点的访问时间:

2:根据记录的节点的访问时间降序遍历反向图,得到的每个连通块就是一个强连通分量:

但是时间复杂度太高!我的代码超时了!在poj刚好过了!

#include"stdio.h"

#include"string.h"

#define N 5005

int n,m;

char g[N][N],vis[N];

int dfn[N],id[N],cnt,dout[N];

void dfs(int u)

{

    vis[u]=1;

    for(int v=1;v<=n;v++)

    {

        if(g[u][v] && !vis[v])

            dfs(v);

    }

    dfn[cnt++]=u;

}

void rdfs(int u)

{

    vis[u]=1;id[u]=cnt;

    for(int v=1;v<=n;v++)

        if(g[v][u] && !vis[v])

            rdfs(v);

}

void solve()

{

    int i,j,t; cnt=0;

    memset(vis,0,sizeof(vis));

    for(i=1;i<=n;i++)

        if(!vis[i])

            dfs(i);

    cnt=0;

    memset(vis+1,0,sizeof(vis));

    for(t=n-1;t>=0;t--)

    {

        i=dfn[t];

        if(!vis[i])

            rdfs(i),cnt++;

    }

    memset(dout,0,sizeof(dout));

    for(i=1;i<=n;i++)

    {

        for(j=1;j<=n;j++) 

            if(g[i][j] && id[i]!=id[j])

                dout[id[i]]++;

    }

    for(i=1;i<=n;i++)

        if(!dout[id[i]])

        {

            printf("%d",i);break;

        }

        for(i++;i<=n;i++)

            if(!dout[id[i]])

                printf(" %d",i);

}

int main()

{

    int a,b;

    while(scanf("%d",&n)&&n)

    {

        memset(g,0,sizeof(g));

        scanf("%d",&m);

        while(m--)

        {

            scanf("%d%d",&a,&b);

            g[a][b]=1;

        }

        solve();

        printf("\n");

    }return 0;

}

转载于:https://www.cnblogs.com/adroitly/archive/2012/08/01/2618842.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值