寫起來簡單無比,不比 Tarjan 香?
方法
- 按照[1...n]的順序在反圖(邊方向相反)上dfs一遍,出棧時將節點存入數組q[1...n]中
- 按照q[n...1]的順序在原圖上dfs一遍,每次遍歷就是一個新的強聯通分量
爲什麼是正確的?
核心在於封死連通分量往外走的路。
如果原圖u-->v有一條邊,且u和v不在同一個強聯通分量裏,那麼反圖v-->u有邊,即u在q序列的位置一定在v的前面,那麼在原圖上逆序遍歷q數組時一定先訪問到v,再訪問u,在dfs(v)時不會訪問到u點(因爲u和v不在同一個強聯通分量裏,v不能到達u),保證了算法的正確性。
核心代碼
struct node{
int v,next;
}e[2][maxn];
void insert(int T,int u,int v){
cnt[T]++;
e[T][cnt[T]].v=v;
e[T][cnt[T]].next=p[T][u];
p[T][u]=cnt[T];
}
void dfs1(int u){
vis[u]=1;
for(int i=p[1][u];i!=-1;i=e[1][i].next){
int v=e[1][i].v;
if(!vis[v]) dfs1(v);
}
q[++tot]=u;
}
void dfs2(int u,int RT){
f[v]=RT;
vis[u]=1;
for(int i=p[0][u];i!=-1;i=e[0][i].next){
int v=e[0][i].v;
if(!vis[v]) dfs2(v,RT);
}
}
void Kosaraju(){
int n=read(),m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read();
insert(0,u,v);//原圖
insert(1,v,u);//反圖
}
memset(vis,0,sizeof(vis));tot=0;
for(int i=1;i<=n;i++) if(!vis[i]) dfs1(i);
memset(vis,0,sizeof(vis));tot=0;
for(int i=n;i>=1;i--) if(!vis[q[i]]) rt[++tot]=q[i],dfs2(q[i],tot);
}