Tarjan算法是由Robert Tarjan(羅伯特·塔揚,不知有幾位大神讀對過這個名字) 發明的求有向圖中強連通份量的算法。ios
預備知識:有向圖,強連通。算法
有向圖:由有向邊的構成的圖。須要注意的是這是Tarjan算法的前提和條件。數組
強連通:若是兩個頂點能夠相互通達,則稱兩個頂點 強連通(strongly connected)。若是有向圖G的每兩個頂點都 強連通,稱G是一個強連通圖。非 強連通圖有向圖的極大強連通子圖,稱爲強連通份量(strongly connected components)。數據結構
For example:學習
在這個有向圖中一、二、三、4四個點能夠互相到達,就稱這四個點組成的子圖爲強連通份量。且這四個點兩兩強連通。spa
而後就能夠開始學習神奇的Tarjan算法了!code
Tarjan算法是用來求強連通份量的,它是一種基於DFS(深度優先搜索)的算法,每一個強連通份量爲搜索樹中的一棵子樹。而且運用了數據結構棧。component
在介紹詳細原理前,先引入兩個很是重要的數組:dfn[ ] 與 low[ ]blog
dfn[ ]:就是一個時間戳(被搜到的次序),一旦某個點被DFS到後,這個時間戳就再也不改變(且每一個點只有惟一的時間戳)。因此常根據dfn的值來判斷是否須要進行進一步的深搜。get
low[ ]:該子樹中,且仍在棧中的最小時間戳,像是確立了一個關係,low[ ]相等的點在同一強連通份量中。
注意初始化時 dfn[ ] = low[ ] = ++cnt.
算法思路:
首先這個圖不必定是一個連通圖,因此跑Tarjan時要枚舉每一個點,若dfn[ ] == 0,進行深搜。
而後對於搜到的點尋找與其有邊相連的點,判斷這些點是否已經被搜索過,若沒有,則進行搜索。若該點已經入棧,說明造成了環,則更新low.
在不斷深搜的過程當中若是沒有路可走了(出邊遍歷完了),那麼就進行回溯,回溯時不斷比較low[ ],去最小的low值。若是dfn[x]==low[x]則x能夠看做是某一強連通份量子樹的根,也說明找到了一個強連通份量,而後對棧進行彈出操做,直到x被彈出。
先來一波局部代碼加深一下理解:
void tarjan(int now) { dfn[now]=low[now]=++cnt; //初始化 stack[++t]=now; //入棧操做 v[now]=1; //v[]表明該點是否已入棧 for(int i=f[now];i!=-1;i=e[i].next) //鄰接表存圖 if(!dfn[e[i].v]) //判斷該點是否被搜索過 { tarjan(e[i].v); low[now]=min(low[now],low[e[i].v]); //回溯時更新low[ ],取最小值 } else if(v[e[i].v]) low[now]=min(low[now],dfn[e[i].v]); //一旦遇到已入棧的點,就將該點做爲連通量的根 //這裏用dfn[e[i].v]更新的緣由是:這個點可能 //已經在另外一個強連通份量中了但暫時還沒有出棧,所 //以now不必定能到達low[e[i].v]但必定能到達 //dfn[e[i].v]. if(dfn[now]==low[now]) { int cur; do { cur=stack[t--]; v[cur]=false; //不要忘記出棧 }while(now!=cur); } }
手動模擬一下過程:
從1進入 dfn[1]= low[1]= ++cnt = 1
入棧 1
由1進入2 dfn[2]=low[2]= ++cnt = 2
入棧 1 2
以後由2進入4 dfn[4]=low[4]= ++cnt = 3
入棧 1 2 4
以後由4進入 6 dfn[6]=low[6]=++cnt = 4
入棧 1 2 4 6
6無出度,以後判斷 dfn[6]==low[6]
說明6是個強連通份量的根節點:6及6之後的點出棧並輸出。
回溯到4後發現4找到了一個已經在棧中的點1,更新 low [ 4 ] = min ( low [ 4 ] , dfn [ 1 ] )
因而 low [ 4 ] = 1 .
由4繼續回到2 Low[2] = min ( low [ 2 ] , low [ 4 ] ).
low[2]=1;
由2繼續回到1 判斷 low[1] = min ( low [ 1 ] , low [ 2 ] ).
low[1]仍是 1
而後更新3的過程省略,你們能夠本身手動模擬一下。
。。。。。。。。。
省略了1->3的更新過程以後,1的全部出邊就跑完了
因而判斷:low [ 1 ] == dfn [ 1 ] 說明以1爲根節點的強連通份量已經找完了。
將棧中1以及1以後進棧的全部點,都出棧並輸出。
End
完整代碼以下:
#include<iostream> //輸出全部強連通份量 #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,m,x,y,top=0,cnt=0,t,col; int ans1=-1,ans2=-1,ans3=-1; int d[200020]; int a[200020]; int c[200020]; int f[200020]; int dfn[200020]; int low[200020]; int stack[200020]; bool v[200020]; struct edge{ int u; int v; int w; int next; }e[1000020]; void Add(int u,int v,int w) { ++top; e[top].u=u; e[top].v=v; e[top].w=w; e[top].next=f[u]; f[u]=top; } int read() { int x=0; int k=1; char c=getchar(); while(c>'9'||c<'0') { if(c=='-') k=-1; c=getchar(); } while(c>='0'&&c<='9') x=x*10+c-'0', c=getchar(); return x*k; } void tarjan(int now) { dfn[now]=low[now]=++cnt; stack[++t]=now; v[now]=1; for(int i=f[now];i!=-1;i=e[i].next) if(!dfn[e[i].v]) { tarjan(e[i].v); low[now]=min(low[now],low[e[i].v]); } else if(v[e[i].v]) low[now]=min(low[now],dfn[e[i].v]); int cur; if(dfn[now]==low[now]) { do { cur=stack[t--]; v[cur]=false; printf("%d ",cur); }while(now!=cur); printf("\n"); } } int main() { n=read(); m=read(); memset(f,-1,sizeof f); for(int i=1;i<=n;++i) a[i]=read(); for(int i=1;i<=m;++i) { x=read(); y=read(); Add(x,y,0); } for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i); return 0; }