【强连通分量】:
有向图强连通分量:
在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。
如果有向图G的每两个顶点都强连通,则称G是一个强连通图。
非强连通图有向图的极大强连通子图,成为强连通分量(strongly connected components)。
直接根据定义,用双向遍历取交际的方法求强连通分量,时间复杂度为O(N^2+M)。更好的方法是Kosaraju算法或者Tarjan算法。
两者的时间复杂度都是O(N+M)。本文介绍的是Tarjan算法。
【算法原理】:(Tarjan)
Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一颗子树。
搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以盘对栈顶到栈中的节点是否为一个强连通分量。
定义DFN(u)为节点u搜索的次序编号(时间戳)。Low(u)为u或者u的子树能够追溯到的最早的栈中的节点的次序号。
由定义可以得出:
Low(u)= Min { DFN(u), Low(v)} ((u,v)为树枝边,u为v的父节点DFN(v),(u,v)为指向栈中节点的后向边(非横叉边))
当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
【算法时空复杂度】:O(N+M)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 1010
using namespace std;
int n,m,c=1,top=1,d=1,s=0,ans=0;
int cstack[MAXN],deep[MAXN],low[MAXN],head[MAXN],colour[MAXN],num[MAXN];//deep数组就是 时间戳dfn数组,且用了数组模拟的栈。。。
bool beque[MAXN];
struct node{
int next,to;
}a[MAXN*5];//前向星存图
inline int read(){
int date=0,w=1;char c=0;
while(c!='-'&&(c<'0'||c>'9'))c=getchar();
if(c=='-'){w=-1;c=getchar();}
while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
return date*w;
}
void add(int x,int y){
a[c].to=y;
a[c].next=head[x];
head[x]=c++;
}
void work(int x){//Tarjan算法
int t;
beque[x]=true;
deep[x]=low[x]=d++;
cstack[top++]=x;
for(int i=head[x];i;i=a[i].next){
t=a[i].to;
if(!deep[t]){
work(t);
low[x]=min(low[x],low[t]);
}
else if(beque[t])
low[x]=min(low[x],deep[t]);
}
if(low[x]>=deep[x]){
s++;
do{
beque[cstack[top-1]]=false;
colour[cstack[top-1]]=s;
}while(cstack[--top]!=x);
}
}
int main(){
int x,y;
n=read();m=read();
memset(num,0,sizeof(num));
for(int i=1;i<=m;i++){
x=read();y=read();
add(x,y);
}
for(int i=1;i<=n;i++)
if(!deep[i])
work(i);
for(int i=1;i<=n;i++){
num[colour[i]]++;
if(num[colour[i]]>num[num[0]])
num[0]=colour[i];
}
for(int i=1;i<=n;i++)
if(colour[i]==num[0])
ans++;
printf("%d\n",ans);//强连通分量中节点个数
for(int i=1;i<=n;i++)
if(colour[i]==num[0])
printf("%d ",i);//强连通分量中各个节点
return 0;
}