算法描述:
在有向图中,我们可能会想知道是否存在一个环,这个环上的任意一个顶点都可以访问同一个环上的其他顶点,这个环上的顶点所组成的集合就叫做一个强连通分量。而将有向图分解成若干个不想交的强连通分量,则叫做强连通分量分解。
算法原理:
1.将有向图存储为G[V]和其反向图rG[V]。
void add_edge(int from,int to){
G[from].push_back(to);
rG[to].push_back(from);
}
2.对G[V]进行dfs,形成其后序遍历顺序的顶点列表vs。
void dfs(int v){
used[v]=true;
for(int i=0;i<G[v].size();i++){
if(!used[G[v][i]])dfs(G[v][i]);
}
vs.push_back(v);
}
3.根据vs中的顺序进行rdfs,每次可获得一个强连通分量。
void rdfs(int v,int k){
used[v]=true;
cmp[v]=k;
SCC[k].push_back(v);
for(int i=0;i<rG[v].size();i++){
if(!used[rG[v][i]])rdfs(rG[v][i],k);
}
}
想象一下,如果步骤3是从森林里选择树,那么哪个树是不连通(对于GT来说)到其他树上的呢?就是最后遍历出来的树,它的根节点在步骤1的遍历中离开时间最晚,而且可知它也是该树中离开时间最晚的那个节点。这给我们提供了很好的选择,在第一次深搜遍历时,记录时间i离开的顶点j,即numb[i]=j。那么,我们每次只需找到没有找过的顶点中具有最晚离开时间的顶点直接深搜(对于GT来说)就可以了。
算法总结:
Kosaraju算法并不是最快的方法,但是很适合初学者理解实现原理。如果有读者感兴趣,可以去了解一下Gabow算法。
测试代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<vector>
#include<algorithm>
#define INF 99999999
#define MAX_V 10000
using namespace std;
int V;
vector<int>G[MAX_V];
vector<int>rG[MAX_V];
vector<int>vs;
vector<int>SCC[MAX_V];
bool used[MAX_V];
int cmp[MAX_V];
void add_edge(int from,int to){
G[from].push_back(to);
rG[to].push_back(from);
}
void dfs(int v){
used[v]=true;
for(int i=0;i<G[v].size();i++){
if(!used[G[v][i]])dfs(G[v][i]);
}
vs.push_back(v);
}
void rdfs(int v,int k){
used[v]=true;
cmp[v]=k;
SCC[k].push_back(v);
for(int i=0;i<rG[v].size();i++){
if(!used[rG[v][i]])rdfs(rG[v][i],k);
}
}
int scc(){
memset(used,0,sizeof(used));
vs.clear();
for(int i=1;i<V;i++){
if(!used[i])dfs(i);
}
memset(used,0,sizeof(used));
int k=0;
for(int i=vs.size()-1;i>=0;i--){
if(!used[vs[i]])rdfs(vs[i],k++);
}
return k;
}
void init(){//初始化有向图
V=13;
add_edge(4,1);
add_edge(2,3);
add_edge(3,2);
add_edge(4,3);
add_edge(6,3);
add_edge(6,4);
add_edge(6,5);
add_edge(5,7);
add_edge(7,6);
add_edge(9,7);
add_edge(9,8);
add_edge(8,10);
add_edge(10,9);
add_edge(11,8);
add_edge(11,10);
add_edge(12,11);
}
int main(){
init();
int ccount=scc();
for(int i=0;i<ccount;i++){
for(int j=0;j<SCC[i].size();j++){
if(j==0)printf("连通分量%d:%d",i,SCC[i][j]);
else printf(" %d",SCC[i][j]);
}
printf("\n");
}
return 0;
}