强连通分量分解--Kosaraju算法分析与证明

定义:在一个有向图的顶点子集V中,如果在V任意选取两个顶点uv均能找到一条从uv的路径,那么称V是强连通的。

若在顶点集任意添加顶点集合后,不再是强连通,则称之为原图的一个强连通分量。


把所有分解后的强连通分量单独看成一个点,则是一个有向无环图。


我这里介绍的是Kosaraju算法,这个算法代码简介,并且是线性的时间复杂度,也比较容易理解,但是严格证明还是有些麻烦,这里参考的是《算法导论》一书中的证明。

算法简介:对图GVE使用一次DFS,记录每个节点的完成时间(即节点出栈的顺序),f[i]然后按照节点被访问的顺序从大到小在图GTVET)(图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个强连通分量去。用数学语言描述为:

CC‘是图G的两个强连通分量,若存在路径(u→ v 属于ET)且(u属于Cv属于C’),那么必有f[C ] < f[ C' ]. 即因为计算顺序是按照f[i]递减的顺序,因此保证了在计算第i个强连通分量的时候,不会存在从这里到第i+j(j > 0 )个强连通分量的路径,所以保证了此时的一次DFS能够求出第i个强连通分量。


3.现简单证明上述定理:

因为f[i ]是原图G的顺序,故直接将定理转成G上证明。因为在GT中存在从CC'的路径,那么在G图中相应存在从C‘C的路径。

CC‘是图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号节点时,CC‘中其余所有节点都没有被访问。但现有一条从C'C的路径(v→ u ,那么说明必定没有从CC'的路径,不然CC’就属于同一个连通分量了,现在从x开始访问,因为在C中访问时,无法进入C’中访问,故必须在C中全部访问结束才进入C‘中访问,因此f[C]< f[ C' ].

2)假设d[C ] > d[ C' ],假定是第x个节点(x属于C’),d[x ] = d[ C'],那么在访问x号节点时,CC‘中其余所有节点都没有被访问。现从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 ; //强连通分量的数量
}
//


参考资料:《挑战程序设计竞赛》,《算法导论》

题目推荐:POJ2186POj3180POj1236



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值