定义:在一个有向图的顶点子集V中,如果在V中任意选取两个顶点u和v均能找到一条从u到v的路径,那么称V是强连通的。
若在顶点集任意添加顶点集合后,不再是强连通,则称之为原图的一个强连通分量。
把所有分解后的强连通分量单独看成一个点,则是一个有向无环图。
我这里介绍的是Kosaraju算法,这个算法代码简介,并且是线性的时间复杂度,也比较容易理解,但是严格证明还是有些麻烦,这里参考的是《算法导论》一书中的证明。
算法简介:对图G(V,E)使用一次DFS,记录每个节点的完成时间(即节点出栈的顺序),f[i];然后按照节点被访问的顺序从大到小在图GT(V,ET)(图G的转置)使用一次DFS,每次DFS的节点就在同一个强连通分量中。
(图GT说明:即将图G中所有边反向构成的一个图)。
证明:
1.几个量的说明,对于一个强连通分量顶点集C,定义d[C]= min{d[i]},其中d[i]表示第i个节点被访问的时间(i属于C集合);定义f[C]= max{ f[i] },其中
f[i]表示第i个节点完成的时间(即出栈的时间)(i属于C集合)。
2.该算法在第二次DFS时就能够求出强连通分量集,那么就说明在处理第i个强连通分量时候,没有路径能够从第i个强连通分量到i+1个强连通分量去。用数学语言描述为:
若C、C‘是图G的两个强连通分量,若存在路径(u→ v 属于ET)且(u属于C,v属于C’),那么必有f[C ] < f[ C' ]. 即因为计算顺序是按照f[i]递减的顺序,因此保证了在计算第i个强连通分量的时候,不会存在从这里到第i+j(j > 0 )个强连通分量的路径,所以保证了此时的一次DFS能够求出第i个强连通分量。
3.现简单证明上述定理:
因为f[i ]是原图G的顺序,故直接将定理转成G上证明。因为在GT中存在从C到C'的路径,那么在G图中相应存在从C‘到C的路径。
若C、C‘是图G的两个强连通分量,若存在路径(v→ u 属于E)且(v属于C',u属于C),那么必有f[C ] < f[ C' ].
(1)假设d[C ] < d [C'],假定是第x个节点(x属于C),d[x ] = d[ C ],那么在访问x号节点时,C、C‘中其余所有节点都没有被访问。但现有一条从C'到C的路径(v→ u ),那么说明必定没有从C到C'的路径,不然C和C’就属于同一个连通分量了,现在从x开始访问,因为在C中访问时,无法进入C’中访问,故必须在C中全部访问结束才进入C‘中访问,因此f[C]< f[ C' ].
(2)假设d[C ] > d[ C' ],假定是第x个节点(x属于C’),d[x ] = d[ C'],那么在访问x号节点时,C、C‘中其余所有节点都没有被访问。现从x开始访问,故先在C‘中访问,因为从C’中有到C的路径(u→ v),因此将会在将C中所有节点访问完之后再回朔到起始位置x,故至少有f[x ] > f[ C ],即f[C' ] > f[ C ].
4.算法模板
//强连通分量的分解算法
//模板来自:《挑战程序设计竞赛》
//O(|V|+|E|),认定边是0~v-1
const int MAXV = 500 ;
int V, n ;
double r ;
vector<int> G[MAXV] ; //图的邻接表表示
vector<int> rG[MAXV] ; //将所有边反向得到的图
vector<int> vs ; //出栈序号f[i]
bool used[MAXV] ;
int cmp[MAXV] ; //统计每个顶点所处的连通分量序号,cmp[i]=j,表示第i号顶点属于第j号连通分量
//加边
void add_edge(int from, int to){
G[from].push_back(to) ;
rG[to].push_back(from) ;
}
//G图
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) ;
}
//GT图
void rdfs(int v, int k){
used[v] = true ;
cmp[v] = k ;
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 v = 0; v < V; v++ ){
if( !used[v] ) dfs(v) ;
}
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 ; //强连通分量的数量
}
//
参考资料:《挑战程序设计竞赛》,《算法导论》
题目推荐:POJ2186,POj3180,POj1236