對於強連通相關概念可參考這裡
推薦BYV大牛的文章
O(n+m)
http://www.byvoid.com/blog/scc-tarjan/
****************************************************************************************************************************************************************************************
以下轉自【http://www.cnblogs.com/saltless/archive/2010/11/08/1871430.html】
說到以Tarjan命名的演算法,我們經常提到的有3個,其中就包括本文所介紹的求強連通分量的Tarjan演算法。而提出此演算法的普林斯頓大學的Robert E Tarjan教授也是1986年的圖靈獎獲得者(具體原因請看本博“曆屆圖靈獎得主”一文)。
首先明確幾個概念。
- 強連通圖。在一個強連通圖中,任意兩個點都通過一定路徑互相連通。比一是一個強連通圖,而圖二不是。因為沒有一條路使得點4到達點1、2或3。
- 強連通分量。在一個非強連通圖中極大的強連通子圖就是該圖的強連通分量。比三中子圖{1,2,3,5}是一個強連通分量,子圖{4}是一個強連通分量。
關於Tarjan演算法的虛擬碼和流程示範請到我的115網盤下載網上某大牛寫的Doc(地址:http://u.115.com/file/f96af404d2<Tarjan演算法.doc>)本文著重從另外一個角度,也就是針對tarjan的操作規則來講解這個演算法。
其實,tarjan演算法的基礎是DFS。我們準備兩個數組Low和Dfn。Low數組是一個標記數組,記錄該點所在的強連通子圖所在搜尋子樹的根節點的Dfn值(很繞嘴,往下看你就會明白),Dfn數組記錄搜尋到該點的時間,也就是第幾個搜尋這個點的。根據以下幾條規則,經過搜尋遍曆該圖(無需回溯)和對棧的操作,我們就可以得到該有向圖的強連通分量。
- 數組的初始化:當首次搜尋到點p時,Dfn與Low數組的值都為到該點的時間。
- 堆棧:每搜尋到一個點,將它壓入棧頂。
- 當點p有與點p’相連時,如果此時(時間為dfn[p]時)p’不在棧中,p的low值為兩點的low值中較小的一個。
- 當點p有與點p’相連時,如果此時(時間為dfn[p]時)p’在棧中,p的low值為p的low值和p’的dfn值中較小的一個。
- 每當搜尋到一個點經過以上操作後(也就是子樹已經全部遍曆)的low值等於dfn值,則將它以及在它之上的元素彈出棧。這些出棧的元素組成一個強連通分量。
- 繼續搜尋(或許會更換搜尋的起點,因為整個有向圖可能分為兩個不連通的部分),直到所有點被遍曆。
由於每個頂點只訪問過一次,每條邊也只訪問過一次,我們就可以在O(n+m)的時間內求出有向圖的強連通分量。但是,這麼做的原因是什麼呢?
Tarjan演算法的操作原理如下:
- Tarjan演算法基於定理:在任何深度優先搜尋中,同一強連通分量內的所有頂點均在同一棵深度優先搜尋樹中。也就是說,強連通分量一定是有向圖的某個深搜樹子樹。
- 可以證明,當一個點既是強連通子圖Ⅰ中的點,又是強連通子圖Ⅱ中的點,則它是強連通子圖Ⅰ∪Ⅱ中的點。
- 這樣,我們用low值記錄該點所在強連通子圖對應的搜尋子樹的根節點的Dfn值。注意,該子樹中的元素在棧中一定是相鄰的,且根節點在棧中一定位於所有子樹元素的最下方。
- 強連通分量是由若干個環組成的。所以,當有環形成時(也就是搜尋的下一個點已在棧中),我們將這一條路徑的low值統一,即這條路徑上的點屬於同一個強連通分量。
- 如果遍曆完整個搜尋樹後某個點的dfn值等於low值,則它是該搜尋子樹的根。這時,它以上(包括它自己)一直到棧頂的所有元素組成一個強連通分量。
模板【vector版】:
vector<int> v[N]; int ans[N]; stack<int> s; bool vis[N]; bool inStack[N]; int low[N],dfn[N];//dfn(u)為節點u搜尋的次序編號(時間戳記),low(u)為u或u的子樹能夠追溯到的最早的棧中節點的序號int belong[N];//屬於哪個強連通分量 int out[N]; int n,m,step,t; void init() { int i; for(i=0;i<=n;i++) { v[i].clear(); } while(!s.empty())s.pop(); step = t = 0; } void tarjan(int u) { vis[u]=true; step++; s.push(u); inStack[u]=true; low[u]=step,dfn[u]=step; int i,j; for(i=0;i<v[u].size();i++) { int x=v[u][i]; if(!vis[x]) { tarjan(x); low[u]=min(low[u],low[x]); } else if(inStack[x]) low[u]=min(low[u],dfn[x]); } if(low[u]==dfn[u]) { t++; while(1) { int x=s.top(); s.pop(); belong[x]=t; inStack[x]=false;//重新設為不在棧中 if(x==u)break; } } }
【二維數組版】
int n; bool g[N][N]; bool vis[N]; bool inStack[N]; int out[N],in[N]; int belong[N]; int low[N],dfn[N]; stack<int> st; int t;//the number of 強連通分量 int step; int min(int a,int b) { return a>b?b:a; } int max(int a,int b) { return a>b?a:b; } void init() { int i,j; memset(g,0,sizeof(g)); memset(vis,0,sizeof(vis)); memset(belong,0,sizeof(belong)); memset(inStack,0,sizeof(inStack)); while(!st.empty())st.pop(); step = t = 0;} void tarjan(int u) { int i,j,k; step++; low[u]=step; dfn[u]=step; vis[u]=1; inStack[u]=1; st.push(u); for(i=1;i<=n;i++) { if(g[u][i]) { if(!vis[i]) { tarjan(i); low[u]=min(low[u],low[i]); } else if(inStack[i]) low[u]=min(low[u],dfn[i]); } } if(low[u]==dfn[u]) { t++; while(1) { int a=st.top(); st.pop(); belong[a]=t; inStack[a]=0; if(a==u)break; } } }