定 义
在有向图G中,如果任意两个不同的顶点相互可达,则称该有向图是强连通的。
有向图G的极大强连通子图称为G的强连通分支。
转置图的定义:将有向图G中的每一条边反向形成的图称为G的转置GT 。(注意到原图和GT 的强连通分支是一样的)
Korasaju算法
1.深度优先遍历G,算出每个结点u的结束时间f[u],起点如何选择无所谓。
每个结点的结束时间和开始时间是dfs序,开始时间是此点第一次被遍历到时,结束时间为此点已经没法拓展,从栈中弹出,即已经遍历结束,不懂dfs序,可以看这个dfs序
2.深度优先遍历G的转置图GT ,选择遍历的起点时,按照结点的结束时间从大到小进行。遍历的过程中,
一边遍历,一边给结点做分类标记,每找到一个新的起点,分类标记值就加1。
如果此节点已经在反图中遍历过,就不再从它遍历,挑选下一个结束时间晚的
3. 第2步中产生的标记值相同的结点构成深度优先森林中的一棵树,也即一个强连通分量
至于证明。。。emmm,我没看懂,就自己想了一下
如果正图中 b->a,那么反图中为 a->b,而b的结束时间一定比a要晚,所以先遍历b,如果b->a,则说明在原图中a->b
#include<bits/stdc++.h>
using namespace std;
int f[1000][1000];//存储正图
int rf[1000][1000];//存储反图
int vis[1000];
stack<int> s;//用来存储节点离开时间
stack<int> s1[1000];
int n;
//对原图dfs,找出每个节点的离开时间,用栈存储,直接pop,就不用再逆序
void dfs(int a){
vis[a]=1;
for(int i=1;i<=n;i++){
if(f[a][i]&&!vis[i]){
dfs(i);
}
}
s.push(a);
}
void rdfs(int a,int k){
vis[a]=1;
for(int i=1;i<=n;i++){
if(rf[a][i]&&!vis[i]){
rdfs(i,k);
}
}
s1[k].push(a);//对每个连通分量分支,记录下来
}
int main(){
int m;
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
f[x][y]=1;
rf[y][x]=1;
}
//对原图dfs
for(int i=1;i<=n;i++){
if(!vis[i])
dfs(1);
}
memset(vis,0,sizeof(vis));
int k=0;
//对反图dfs
while(!s.empty()){
if(!vis[s.top()])//节点按照节点离开时间从大到小遍历
rdfs(s.top(),++k);//计算连通分量个数
s.pop();
}
cout<<k<<endl;
//输出
for(int i=1;i<=k;i++){
while(!s1[i].empty()){
cout<<s1[i].top();
s1[i].pop();
}
cout<<endl;
}
return 0;
}
用邻接表写的代码(老师写的)
#include <cstdio>
#include <cstring>
const int V = 1e4 + 7, E = 5e4 + 7;
int hd[V], to[E], fr[E], nt[E], pr[E], tl[E];