強連通分量(Tarjan演算法)

不想悲傷到天明發表於2019-02-22

強連通分量

       有向圖強連通分量:在有向圖G中,如果兩個頂點vi,vj間(vi>vj)有一條從vi到vj的有向路徑,同時還有一條從vj到vi的有向路徑,則稱兩個頂點強連通。如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱為強連通分量

 

        Tarjan 演算法是基於對圖優先搜素的演算法 , 每個強連通分量為搜尋樹中的一棵子樹 .搜尋時,把當前搜尋樹中未處理的節點加入一個堆疊 ,回溯時可以判斷棧頂到棧中節點是否為一個強連通分量 . 

         定義 dfn[u] 為節點u搜尋的次序編號(時間戳) ,low[u]  為u 或u的子樹能夠追溯到的最早的棧中節點的次序號 . 

         當dfn[u] = low[u]  時 , 以 u 為根的搜尋子樹上所有節點時一個強連通分量 , 虛擬碼 : 

 

演算法流程 :

 模板 :  https://www.luogu.org/problemnew/show/P2863

鄰接表存圖

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <set>
#include <map>
#include <vector>

using namespace std;
const int MAX = 10010 ;
vector <int> g[MAX] ;
int color[MAX] ;
int dfn[MAX] ; // 表示dfs時第幾個被搜到的
int low[MAX] ; // 表示這個點以及其子孫節點連的所有點中dfn最小的值
int stack[MAX] ; //當前所有可能能構成強連通分量的點
int vis[MAX] ; // 標記
int cnt[MAX] ;
int deep ,top , n , m, sum , ans ;
void tarjan(int u ){
	dfn[u] = ++deep ;
	low[u] = deep ;
	vis[u] = 1 ;// 記錄是否在棧中
	stack[++top] = u ;
	for(int i = 0 ; i<int(g[u].size()) ;i++){
		int v = g[u][i] ; // 鄰接的子孫節點  ;
		if(!dfn[v]){
			tarjan(v) ;
			low[u] = min(low[u],low[v]) ;
		}
		else{
			if(vis[v]){
				low[u] = min(low[u],dfn[v]) ;
			}
		}
	}
	if(dfn[u]==low[u]){
		color[u]= ++sum ;
		vis[u] = 0 ;
		while(stack[top]!=u){
			color[stack[top]] = sum ;
			vis[stack[top--]] = 0 ;
		}
		top--  ;
	}

}
int main(){
    
    scanf("%d%d",&n,&m);
	for(int i = 1 ; i <= m ;i++){
		int u , v ;
		scanf("%d%d",&u,&v);
		g[u].push_back(v) ;
	}
	for(int i = 1 ; i<=n ; i++ ){
		if(!dfn[i]){
			tarjan(i) ;
		}
	}
	for(int i = 1 ; i<=n ; i++ ){
		cnt[color[i]]++ ;
	}
	for(int i = 1 ; i<=sum ; i++ ){
		if(cnt[i]>1){
			ans++ ;
		}
	}
	cout<<ans ;
    return 0;

}

鏈式前向星 

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <set>
#include <map>
#include <vector>
#include <stack>
using namespace std;
const int MAX = 100100 ;
int n , m , idx  ;
int cnt  ,Bcnt  ;
int head[MAX] ;
int vis[MAX] ;
int dfn[MAX] ,low[MAX] ;
int Belong[MAX] ;
stack<int> s ;
struct edge{
	int v ;
	int next ;
}e[MAX] ;
void add(int u , int v){
	e[++cnt].v = v ;
	e[cnt].next = head[u] ;
	head[u] = cnt++ ;
}
void tarjan(int u ){
	int v;
	dfn[u] = low[u] = ++idx ; // 每次dfs , u的次序號加一
	s.push(u) ; // 將u 進棧
	vis[u] = 1 ; // 標記u已經在棧內
	for(int i = head[u] ;i!=-1 ; i= e[i].next ){
		v = e[i].v;
		if(!dfn[v]){ // 如果還沒處理過
			tarjan(v) ;
			low[u] = min(low[u],low[v]) ;//如果v在棧內,u點能到達的最小次序號是它自己能到達點的
			// 最小次序號和v的次序號中較小的
		}
		else{
			if(vis[v]){
				low[u] = min(low[u],dfn[v]) ;
			}
		}
	}
	if(dfn[u] == low[u]){
		Bcnt++ ;
		do{
			v = s.top() ;
			s.pop() ;
			vis[v] = 0 ;
			Belong[v] = Bcnt ;
		}while(u!=v) ;
	}
	
	
}
int main(){
	
    cin >> n >> m ;
    memset(head,-1,sizeof(head)) ;
    for(int i = 1 ; i<=m ; i++ ){
    	int u , v;
    	cin >>u >>v ;
    	add(u,v) ;
	}
	for(int i = 1 ; i<=n ; i++){
		if(!dfn[i]){
			tarjan(i) ;
		}
	}
//	cout<<endl;
//	for(int i = 1 ; i<=6 ; i++){
//		cout<<dfn[i] <<" "<<low[i] <<endl ;
//	}
// 	cout<<" 有"<<Bcnt<<" 個強連通分量 "<<endl  ;
// 	for(int i = 1 ; i<=Bcnt; i++ ){
// 		cout<<i<<" : " ;
// 		for(int j = 1 ; j<=n ; j++){
// 			if(Belong[j]==i){
// 				cout<<j <<" " ;
//			 }
//		 }
//		 cout<<endl;
//	 }
	int ans = 0 ;
	for(int i = 1 ; i<=Bcnt ; i++ ){
		int num = 0 ;
		for(int j = 1; j<=n ; j++ ){
			if(Belong[j]==i){
				num++ ;
			}
		}
		if(num>1){
			ans++ ;
		}
	}
	cout<<ans;

    return 0;

}

 

 

 

 

相關文章