tarjan algorithm
form:Christopher Yan
概念
如果两个顶点可以相互通达,则称两个顶点强连通
如果有向图 \(G\) 的每两个顶点都强连通,称 \(G\) 是一个强连通图.
非强连通图有向图的极大强连通子图,称为强连通分量
\(Tarjan\) 算法是用来求强连通分量的,它是一种基于\(DFS\)(深度优先搜索)的算法,每个强连通分量为搜索树中的一棵子树。并且运用了数据结构栈。
在介绍详细原理前,先引入两个非常重要的数组:\(dfn[ ]\) 与 \(low[ ]\)
\(dfn[ ]\):就是一个时间戳(被搜到的次序),一旦某个点被 \(DFS\) 到后,这个时间戳就不再改变(且每个点只有唯一的时间戳)。所以常根据 \(dfn\) 的值来判断是否需要进行进一步的深搜
\(low[ ]\):该子树中,且仍在栈中的最小时间戳,像是确立了一个关系,\(low[ ]\) 相等的点在同一强连通分量中。
注意初始化时 \(dfn[ ] = low[ ] = ++cnt.\)
算法思路:
首先这个图不一定是一个连通图,所以跑 \(Tarjan\) 时要枚举每个点,若 \(dfn[ ] == 0\),进行深搜。
然后对于搜到的点寻找与其有边相连的点,判断这些点是否已经被搜索过,若没有,则进行搜索。若该点已经入栈,说明形成了环,则更新 \(low\).
在不断深搜的过程中如果没有路可走了(出边遍历完了),那么就进行回溯,回溯时不断比较 \(low[ ]\),去最小的 \(low\) 值。如果 \(dfn[x]==low[x]\) 则 \(x\) 可以看作是某一强连通分量子树的根,也说明找到了一个强连通分量,然后对栈进行弹出操作,直到 \(x\) 被弹出
手动模拟一下过程:
从1进入 dfn[1]= low[1]= ++cnt = 1
入栈 1
由1进入2 dfn[2]=low[2]= ++cnt = 2
入栈 1 2
之后由2进入4 dfn[4]=low[4]= ++cnt = 3
入栈 1 2 4
之后由4进入 6 dfn[6]=low[6]=++cnt = 4
入栈 1 2 4 6
6无出度,之后判断 \(dfn[6]==low[6]\)
说明6是个强连通分量的根节点:\(6\) 及 \(6\) 以后的点出栈并输出。
回溯到4后发现4找到了一个已经在栈中的点 \(1\),更新 low [ 4 ] = min ( low [ 4 ] , dfn [ 1 ] )
于是 low [ 4 ] = 1 .
由4继续回到2 Low[2] = min ( low [ 2 ] , low [ 4 ] ).
low[2]=1;
由2继续回到1 判断 low[1] = min ( low [ 1 ] , low [ 2 ] ).
low[1]还是 1
然后更新3的过程省略,大家可以自己手动模拟一下。
省略了 \(1->3\) 的更新过程之后,\(1\)的所有出边就跑完了
于是判断: \(low [ 1 ] == dfn [ 1 ]\) 说明以 \(1\) 为根节点的强连通分量已经找完了。
将栈中 \(1\) 以及 \(1\) 之后进栈的所有点,都出栈并输出
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
int x = 0, f = 1;char ch = getchar();
while (ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}
int n,m,x,y,top=0,cnt=0,t,col;
int ans1=-1,ans2=-1,ans3=-1;
int d[200020];
int a[200020];
int c[200020];
int f[200020];
int dfn[200020];
int low[200020];
int stack[200020];
bool v[200020];
struct edge{
int u;
int v;
int w;
int next;
}e[1000020];
void Add(int u,int v,int w){
e[++top].v=v;
e[top].w=w;
e[top].next=f[u];
f[u]=top;
}
void tarjan(int now)
{
dfn[now]=low[now]=++cnt;
stack[++t]=now;
v[now]=1;
for(int i=f[now];i!=-1;i=e[i].next)
if(!dfn[e[i].v]) {
tarjan(e[i].v);
low[now]=min(low[now],low[e[i].v]);
}
else if(v[e[i].v])
low[now]=min(low[now],dfn[e[i].v]);
int cur;
if(dfn[now]==low[now]){
do
{
cur=stack[t--];
v[cur]=false;
printf("%d ",cur);
}while(now!=cur);
printf("\n");
}
}
int main()
{
n=read();
m=read();
memset(f,-1,sizeof f);
for(int i=1;i<=n;++i)
a[i]=read();
for(int i=1;i<=m;++i)
{
x=read();
y=read();
Add(x,y,0);
}
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i);
return 0;
}