如果对原图进行深度优先搜索,由强连通分量定义已知,任何一个强连通分量是原图的深度优先搜索树的子树。那么,只要确定每个强连通分量子树的根,然后跟据这些根从树的最低层,一个一个取出强连通分量即可。问题就是如何确定强连通分量的根和如何从最低层开始拿出强连通分量了。
如何确定强连通分量的根,在这里维护两个数组,一个是dfn[maxn],另一个是low[maxn],其中dfn[v]表示顶点v被访问的时间,low[v]为与顶点v邻接的未删除的顶点u的low[u]和low[v]的最小值(low[v]初始化未dfn[v])。这样,在一次深度优先搜索的回溯过程中,如果发现low[v]==dfn[v],那么当前顶点就是一个强连通分量的根。因为如果不是强连通分量的根,那么一定是属于另一个强连通分量,而且它的根是当前顶点的祖宗,那么存在包含当前顶点的到其祖宗的回路,可知low[v]一定被更改为一个比dfn[v]更小的值。
对于如何取出强连通分量,如果当前节点为一个强连通分量的根,那么它的强连通分量一定是以该根为根节点的子树(剩下节点)子树。在深度优先遍历中维护一个堆栈,每次访问一个新节点,就压入堆栈。由于当前节点是这个强连通分量中最先被压入堆栈的,那么在当前节点以后压入堆栈的并且仍在堆栈中的节点都属于这个强连通分量。假设一个节点在当前节点压入堆栈以后压入并且还存在,同时不属于该连通分量,那么一定属于另一个强连通分量,但当前节点是其根的祖宗,那么这个强连通分量应该在此之前已经被取出。
时间复杂度分析:时间复杂度为O(n+m)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1001;
struct edge{
int v,next;
};
edge edges[maxn];
int head[maxn],cnt;
int dfn[maxn],low[maxn],stack[maxn],visit[maxn],tot,index;
void add(int x,int y)
{
edges[cnt].v=y;
edges[cnt].next=head[x];
head[x]=cnt++;
}
void tarjan(int x)
{
dfn[x]=low[x]=++tot;
stack[++index]=x;
visit[x]=1;
for(int i=head[x];~i;i=edges[i].next){
if(!dfn[edges[i].v]){
tarjan(edges[i].v);
low[x]=min(low[x],low[edges[i].v]);
}else if(visit[edges[i].v]){
low[x]=min(low[x],dfn[edges[i].v]);
}
}
if(low[x]==dfn[x]){
do{
printf("%d ",stack[index]);
visit[stack[index]]=0;
index--;
}while(x!=stack[index+1]);
printf("\n");
}
}
int main()
{
memset(head,-1,sizeof(head));
int n,m;
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
add(u,v);
}
tot=0,index=0;
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i);
}
}
return 0;
}