问题描述:给出一个图,求其中的强连通分量。
分析:常见的算法有tarjan算法和二度深搜法(即Kosaraju算法)
这里详解tarjan算法。
算法核心:
对于一个图,我们给每一个dfs访问的点设立时间戳,即到达这里所需的最小时间,那么如果在树中,显然儿子结点的时间戳一定大于父亲的时间戳,而这里是有环的图,所以如果儿子的时间戳小于父亲的时间戳,则说明在访问父亲之前,就已经到达儿子了,那么就存在环。我们用一个栈维护访问的结点,如果当前节点的儿子结点的时间戳的最小值等于当前结点的时间戳,那么就把压在当前节点以上的点弹出,作为一个强连通分量。
具体步骤:其实上面的说法并不准确,在这里再做严格声明:
dfn[i]表示第i结点的时间戳,low[i]表示第i结点所能到达的点集中时间戳的最小值。
那么对于每个i,我们将其压入栈,dfn[i]=++time,然后枚举i所能到达的j,若j未被访问,则接着dfs(j),回溯时low[i]=min(low[i],low[j]),若j已被访问,且j在栈中,则low[i]=min(low[i],dfn[j]).若不在栈中,那么j已在一个强连通分量中了,而没有访问i,说明i不在j的强连通分量中。
也许你会问,那样j就有可能同时存在在两个强连通分量中,会不会没有考虑,你可以这样想:既然j同时可以在这两个强连通分量中,那说明A强连通分量都可以到达j,B亦如此,j可以到达A强连通分量,B亦如此,那么是不是A和B就构成一个更大的强连通分量了呢?
参考程序:
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=110000;
int n,m;
struct Node{
int j,next;
}e[maxn];
int a[maxn],ti[maxn],low[maxn];
int stack[maxn],top=0;
int ans=0,TIME=0,NodeCnt=0,CaseCnt=0;
bool instack[maxn];
void tarjan(int u){
low[u]=ti[u]=++TIME;
stack[++top]=u;instack[u]=true;
for (int p=a[u];p;p=e[p].next){
int v=e[p].j;
if (!ti[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}else if (instack[u])low[u]=min(low[u],ti[v]);
}
if (low[u]==ti[u]){
int res=0;
//CaseCnt++;
while (stack[top]!=u && top>0)
instack[stack[top]]=false,res++,top--;
res++;top--;instack[u]=false;
ans=max(ans,res);
}
}
int main(){
scanf("%d%d",&n,&m);
for (int i=0;i<m;i++){
int u,v;
scanf("%d%d",&u,&v);
int p=++NodeCnt;
e[p].j=v;e[p].next=a[u];a[u]=p;
}
memset(ti,0,sizeof(ti));
memset(instack,0,sizeof(instack));
ans=0;
for (int i=1;i<=n;i++)
if (!ti[i])tarjan(i);
printf("%d",ans);
return 0;
}
而对于二度深搜法,其核心时建立一个正向一个反向图,如果在正向图中i能到j,反向图中i还能到j,则说明其在同一个强连通分量中。这里就不作详述。