Kosaraju 算法学习笔记(求强连通分量)
写起来简单无比,不比 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);
}