1. 程式人生 > >Tarjan演算法之求強連通分量

Tarjan演算法之求強連通分量

最近又學習了強連通分量的Tarjan求法,先是看了別人的許多部落格,才勉勉強強看懂,自己寫完部落格後感到十分顯然,也沒有表面上看的那麼高大上。

好了,轉入正題,先說說什麼是強連通分量:

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

簡單來說,就是一個有向圖中,有一個子圖,子圖中的任意兩個點都能互相到達,則這個子圖就是整個圖的強聯通分量,這麼來說,顯然的是,整張圖一定是被分割成了幾個強連通分量,我們舉個例子看一下:

顯而易見的1,2,3號節點是可以互相到達的,所以1,2,3組成的子圖是強聯通分量,此外,5和4號節點單獨成為一個強連通分量,於是,這張圖就有了三個強連通分量至於怎麼微妙地求出這幾個強連通分量,這裡用的是Tarjan演算法,看完之後就會恍然大悟的。

首先,我們定義一個數組,dfn[],這個陣列存的是當前節點的時間戳(什麼是時間戳?時間戳就是當前節點的DFS序,也可以理解為第一次遍歷到當前節點的時間),還有low[]陣列,這個陣列存的是當前節點能夠追溯到的時間戳最小的節點的時間戳,也就相當於這個節點所在的強連通分量的“根節點”。可能有點難理解,多讀幾遍,說不定能理解一點。

那麼low【】陣列和dfn【】陣列要如何維護呢?

dfn【】陣列,顯然,在遍歷到該節點時記錄,以後就不用動了,而low【】陣列,是在擴充套件點時更新的,比如說,當前節點為u,將要擴充套件的點為v,若dfn[v]==low[v]&&vis[v]==false,也就是說,v點是可以到達u點的(因為u點沒有被訪問結束,說明u點是由v點擴展出來的),這樣就出現了一個環。!!!注意!!!有環就有強連通分量,想想是不是?環上的所有的點組成了一個強連通分量,之後只要vis[u]=false,low[u]=low[v],漸漸地回溯並找其他的點即可

我們還是以上圖為例:

假設從1點開始遍歷,dfn[1]=1,low[1]=1,繼續2點,dfn[2]=2,low[2],繼續3點,dfn[3]=3,low[3]=3,繼續深搜,發現下一個點是1,有趣,滿足條件dfn[v]==low[v]&&vis[v]==false,修改low【3】=1,且沒有要拓展的點,回溯。邊回溯邊修改,low【2】=1,繼續回溯,到一,訪問結束。接著找其他點,繼續訪問。最後訪問的結果應該是這樣的:

i           :1,2,3,4,5。

dfn【】:1,2,3,4,5。

low【】:1,1,1,4,5.

所以一共有三個強連通分量,分別是以1,4,5為根的。程式碼貼上(大佬勿噴):

#include <bits/stdc++.h>

using namespace std;

int dfn[10010],low[10010];
bool vis[10010];
vector<int> son[10010];
int n,m;
int tim=0;
int ans=0;

template <typename T> void read(T &x) {
	x = 0; char c = getchar ();
	for (; !isdigit(c); c = getchar ());
	for (; isdigit(c); c = getchar ()) x = x * 10 + c - '0';
}

void Tarjan(int x){
	vis[x]=true;
	tim++;
	dfn[x]=low[x]=tim;
	for (int i=0;i<son[x].size();i++){
		int v=son[x][i];
		if (!vis[v]) Tarjan(v);
		if (vis[v]) low[x]=min(low[x],low[v]);
	}
	if (dfn[x]==low[x]) ans++;
}

int main() {
	cin>>n>>m;
	for (int i=1;i<=m;i++){
		int x,y;
		read(x);
		read(y);
		son[x].push_back(y);
	}
	for (int i=1;i<=n;i++)
		if (!vis[i]) Tarjan(i);
	for (int i=1;i<=n;i++)
		cout<<low[i]<<' ';
	cout<<endl;
	cout<<ans<<endl;
	return 0;
}

蒟蒻個人理解,有意見可以提。