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=(e1 , ... , en ) be a
sequence of length n of edges ei E such
that ei = (vi ,vi+1 ) for a sequence of vertices ( v1 ,... , 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 ( v1 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 v, v is also reachable
from w. The bottom of a graph is the subset of all nodes that are
sinks, i.e., bottom(G) = { vV | w∈V: (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 v1 , w1 , ... , ve , 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;
}