SCC算法求強連通分量簡單講解證明及實現

強連通分量

連通分量要求任意兩點可達,而強連通分量要求任意兩點互相可達,即必須存在a->b且b->a的路徑

強連通分量問題就是求解一個圖中所有的強連通分量集合。

SCC算法簡介

SCC算法在《算法導論》中有介紹到,SCC算法基於dfs,通過兩次dfs可以求出圖中所有的連通分量,是快速的方法。

在瞭解SCC算法之前先介紹兩個概念

兩個概念

dfs結束時間

規定一種【結束時間】,表示dfs退棧時候的時間,我們可以用兩個數字表示dfs進棧,退棧的時間。如下圖所示,左邊的數字是dfs開始時間,右邊的數字是dfs結束時間。
在這裏插入圖片描述
dfs結束時間可以表示拓撲排序的序列,即結束時間大的,拓撲排序排在結束時間小的節點前面,本質上表示退棧的先後,即訪問順序

轉置圖

即有向圖的出邊改爲入邊
在這裏插入圖片描述

SCC算法僞代碼描述

對原圖dfs並且將節點按照【結束時間】從大到小排序形成序列seq[]
按照seq[]序列裏面的節點順序 對【轉置圖】dfs 搜到的每一個連通分支就是一個強連通分量

?當我打出❓的時候,

這也太抽象了吧,所以下面給出證明

SCC算法正確性證明

證明過程需要一些引理的幫助,下面介紹幾個引理

引理1:

假設有兩個強連通分量c1和c2,並且存在從c1到c2的邊,那麼一定不存在從c2到c1的邊。

在這裏插入圖片描述
證明很簡單,如果存在邊從c2到c1,那麼c1c2就是同一個強連通分量,而不是兩個強連通分量了。

引理2:

如果c1到c2有邊,那麼強連通分支【c1中最晚結束節點】的結束時間,一定晚於【c2中最晚結束節點】的時間。

在正向圖中,c1的結束時間一定晚於c2,那麼有:
在轉置圖中,c2的結束時間一定晚於c1

原因也很簡單,因爲c1一定先於c2被訪問,畢竟要到達c2必先經過c1,轉置圖相當於邊全反過來,所以存在邊從c2到c1,而由引理1,不存在邊從c1到c2了,同理可得c2的結束時間晚於c1。

SCC證明

假設原圖中有強連通分量c1,在轉置圖上按照【原圖中dfs結束時間從大到小】順序dfs,一定能夠找到c1內的所有點。並且不會找到其他強連通分支內

不錯找

先來證明在反向圖上對c1中的點dfs,不會找到其他分支內的點,即不錯找

反證法:假設原圖有強連通分支c1和c2,而我們在轉置圖中對c1內的點dfs,能夠找到c2內的點,這表明轉置圖中有邊c1到c2,也就是說原圖中有邊c2到c1,那麼說明c2所有點的結束時間晚於c1中的所有點,那麼在轉置圖的dfs中,c2的點一定先於c1的點被dfs到,所有c1的點永遠到不了c2的點,即不會【錯找】
在這裏插入圖片描述

不漏找

這個好理解,假設存在x->y表示x到y有路徑

強連通分量中,存在a->b和b->a,那麼將邊全部反向變成轉置圖之後

  1. a->b(原) 變爲 b->a(新)
  2. b->a(原) 變爲 a->b(新)

仍然存在存在 a->b和b->a,是強連通,不漏找

代碼實現

ps:其實第一次dfs,退棧之後的節點裝到一個棧裏面。dfs結束之後,按照順序彈出節點,就是結束時間從晚到早的排序序列

#include <bits/stdc++.h>

using namespace std;

int n, e, ans=0;
vector<vector<int>> adj;
vector<vector<int>> adj_T;	// 轉置圖 
vector<int> vis;
vector<int> seq;	// 存節點 下標越大 結束時間越晚

// dfs原圖 
void dfs(int x)
{
	vis[x] = 1;
	for(int i=0; i<adj[x].size(); i++)
		if(vis[adj[x][i]]==0) dfs(adj[x][i]);
	seq.push_back(x);
}

// dfs轉置圖 
void dfs_T(int x)
{
	vis[x] = 1;
	for(int i=0; i<adj_T[x].size(); i++)
		if(vis[adj_T[x][i]]==0) dfs_T(adj_T[x][i]);
}

int main()
{ 	
	cin>>n>>e;
	adj.resize(n); adj_T.resize(n); vis.resize(n);
	for(int i=0; i<e; i++)
	{
		int st, ed; cin>>st>>ed;
		adj[st].push_back(ed);
		adj_T[ed].push_back(st);
	}
	
	// dfs原圖 
	for(int i=0; i<n; i++) 
		if(vis[i]==0) dfs(i);
	// 重置vis 
	for(int i=0; i<n; i++) vis[i]=0;
	// 按照seq的順序dfs轉置圖 
	for(int i=n-1; i>=0; i--) 
		if(vis[seq[i]]==0) dfs_T(seq[i]),++ans; 
	cout<<"強連通分量個數: "<<ans<<endl;
	
	return 0;
}

/*
6 8
0 1
0 4
1 2
2 0
2 1
3 2
4 5
5 4
*/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章