https://blog.csdn.net/jeryjeryjery/article/details/52829142?locationNum=4&fps=1
以防鏈接失效,特此轉載此博,如有侵權請見諒
?在有向圖G中,如果兩個頂點間至少存在一條路徑,稱兩個頂點強連通(strongly connected)。如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。非強連通圖有向圖的極大強連通子圖,稱為強連通分量(strongly connected components)。如下圖中,強連通分量有:{1,2,3,4},{5},{6}
? ? ? ?Tarjan算法是基于對圖深度優先搜索的算法,每個強連通分量為搜索樹中的一棵子樹。搜索時,把當前搜索樹中未處理的節點加入一個堆棧,回溯時可以判斷棧頂到棧中的節點是否為一個強連通分量。Tarjan算法有點類似于基于后序的深度遍歷搜索和并查集的組合,充分利用回溯來解決問題。
在Tarjan算法中為每個節點i維護了以下幾個變量:
DFN[i]:深度優先搜索遍歷時節點i被搜索的次序。
low[i]:節點i能夠回溯到的最早位于棧中的節點。
flag[i]:標記幾點i是否在棧中。
Tarjan算法的運行過程:
(1).首先就是按照深度優先搜索算法搜索的次序對圖中所有的節點進行搜索。
(2).在搜索過程中,對于任意節點u和與其相連的節點v,根據節點v是否在棧中來進行不同的操作:
*節點v不在棧中,即節點v還沒有被訪問過,則繼續對v進行深度搜索。
*節點v已經在棧中,即已經被訪問過,則判斷節點v的DFN值和節點u的low值的大小來更新節點u的low值。如果節點v的?DFN值要小于節點u的low值,根據low值的定義(能夠回溯到的最早的已經在棧中的節點),我們需要用DFN值來更新u?的low值。
(3).在回溯過程中,對于任意節點u用其子節點v(其實不能算是子節點,只是在深度遍歷的過程中,v是在u之后緊挨著u的節點)的? ?low值來更新節點u的low值。因為節點v能夠回溯到的已經在棧中的節點,節點u也一定能夠回溯到。因為存在從u到v的直接路徑,所以v能夠到的節點u也一定能夠到。
(4).對于一個連通圖,我們很容易想到,在該連通圖中有且僅有一個節點u的DFN值和low值相等。該節點一定是在深度遍歷的過程中,該連通圖中第一個被訪問過的節點,因為它的DFN值和low值最小,不會被該連通圖中的其他節點所影響。
? ? ? 下面我們證明為什么僅有一個節點的DFN和low值相等。假設有兩個節點的DFN值和low值相等,由于這兩個節點的DFN值一定不相同?(DFN值的定義就是深度遍歷時被訪問的先后次序),所以兩個的low值也絕對不相等。由于位于同一個連通圖中,所以兩個節點必定相互可達,那么兩者的low值一定會被另外一個所影響(要看誰的low值更小),所以不可能存在兩對DFN值和low值相等的節點。
? ? ? ?所以我們在回溯的過程中就能夠通過判斷節點的low值和DFN值是否相等來判斷是否已經找到一個子連通圖。由于該連通圖中的DFN值和low值相等的節點是該連通圖中第一個被訪問到的節點,又根據棧的特性(先壓入? 棧的節點在棧的更里面),則該節點在最里面。所以能夠通過不停的彈棧,直到彈出該DFN值和low值相同的節點來彈出該連通圖中所有的節點。
?
Tarjan算法的C++實現代碼如下,可以配合上面的圖加以理解:
?
- #include<iostream>??
- using?namespace?std;??
- int?DFN[105];??????????????????????????????????//記錄在做dfs時節點的搜索次序??
- int?low[105];??????????????????????????????????//記錄節點能夠找到的最先訪問的祖先的記號??
- int?count=1;???????????????????????????????????//標記訪問次序,時間戳??
- int?stack[105];????????????????????????????????//壓入棧中??
- int?top=-1;??
- int?flag[105];?????????????????????????????????//標記節點是否已經在棧中??
- int?number=0;??
- int?j;??
- int?matrix[105][105]={{0,1,1,0,0,0},{0,0,0,1,0,0},{0,0,0,1,1,0},{1,0,0,0,0,1},{0,0,0,0,0,1},{0,0,0,0,0,0}};??
- int?length;????????????????????????????????????//圖的長度??
- void?tarjan(int?u){??
- ????DFN[u]=low[u]=count++;?????????????????????//初始化兩個值,自己為能找到的最先訪問的祖先??
- ????stack[++top]=u;??
- ????flag[u]=1;?????????????????????????????????//標記為已經在棧中??
- ??
- ????for(int?v=0;v<length;v++){??
- ????if(matrix[u][v]){??
- ????????if(!DFN[v]){???????????????????????//如果點i沒有被訪問過??
- ????????tarjan(v);?????????????????????//遞歸訪問??
- ????????if(low[v]<low[u])??
- ????????????low[u]=low[v];?????????????//更新能找的到祖先??
- ????????}??
- ????????else{??????????????????????????????//如果訪問過了,并且該點的DFN更小,則??
- ????????if(DFN[v]<low[u]&&flag[v])?????//flag[v]這個判斷條件很重要,這樣可以避免已經確定在其他聯通圖的v,因為u到v的單向邊而影響到u的low??
- ????????low[u]=DFN[v];?????????????????//也就是已經確定了的聯通圖要剔除掉,剔除的辦法就是判斷其還在棧中,因為已經確定了的連通圖的點??
- ????????}??????????????????????????????????//flag在下面的do?while中已經設為0了(即已經從棧中剔除了)??
- ????}??
- ????}??
- ??
- ????//往后回溯的時候,如果發現DFN和low相同的節點,就可以把這個節點之后的節點全部彈棧,構成連通圖??
- ????if(DFN[u]==low[u]){??
- ????number++;???????????????????????????????//記錄連通圖的數量??
- ????do{??
- ????????j=stack[top--];?????????????????????//依次取出,直到u??
- ????????cout<<j<<"?";??
- ????????flag[j]=0;??????????????????????????//設置為不在棧中??
- ????}while(j!=u);??
- ????????cout<<endl;??
- ????}??
- }??
- int?main(){??
- ??????
- ????memset(DFN,0,sizeof(DFN));??????????????????//數據的初始化??
- ????memset(low,0,sizeof(low));??
- ????memset(flag,0,sizeof(flag));??
- ??????
- ????length=6;??
- ????tarjan(0);??
- ??
- ????cout<<endl;??
- ????for(int?i=0;i<6;i++){??
- ????cout<<"DFN["<<i<<"]:"<<DFN[i]<<"?low["<<i<<"]:"<<low[i]<<endl;??
- ????}??
- ????return?0;??
- } ?