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

圖之強連通、強連通圖、強連通分量 Tarjan算法

The 當前 one 自身 com name cxf 單個 con

原文地址:https://blog.csdn.net/qq_16234613/article/details/77431043

一、解釋

在有向圖G中,如果兩個頂點間至少存在一條互相可達路徑,稱兩個頂點強連通(strongly connected)。如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。非強連通圖有向圖的極大強連通子圖,稱為強連通分量(strongly connected components)。
求解有向圖的強連通分量算法有很多,例如Kosaraju,Gabow和Tarjan算法,其中Gabow和Tarjan算法時間復雜度要優於Kosaraju。
理解:
如果單純將其看出圖的話有點難以理解,但是當我們將其看成樹,就很容易了。
技術分享圖片


如上圖,如果兩個點成強聯通,那麽顯然在樹中就會存在一個環,圖中L-M-J-L和A-L-M-B-A成環所以組成的強聯通分量。

二、Tarjan算法

Tarjan算法基於深度優先搜索樹,其有兩個重要變量DFN[u]:表示在深度搜索中遍歷到該節點的次序。LOW(u)表示以u節點為樹根,u及u以下樹節點所能找到的最小次序號。註意Tarjan認為單個節點自身就是一個強聯通分量,在處理數據時註意屏蔽。以上圖為例,我們從A開始,
A:DFN[1] = 1; LOW(1)=1
L:DFN[2] = 2; LOW(2)=2
M:DFN[3] = 3; LOW(3)=3
J:DFN[4] = 4; LOW(4)=4
這時我們在J節點繼續往下搜索時,發現L節點我們已經搜索過了,且L:LOW(2)=2,我們發現J:LOW(4)=4>L:LOW(2)=2,因此我們將其賦值LOW(4)=2,這說明此時我們發現了一個環,代表一個強聯通分量。
下面繼續:
J:DFN[4] = 4; LOW(4)=2
M:DFN[3] = 3; LOW(3)=2
B:DFN[5] = 4; LOW(5)=5
發現B到A:
B:DFN[5] = 4; LOW(5)=1
開始返回更新:
M:DFN[3] = 3; LOW(3)=1
L:DFN[2] = 2; LOW(2)=1
A:DFN[1] = 1; LOW(1)=1
發現DFN=LOW(1),彈出棧。

void tarjan(int u){

    DFN[u]=LOW[u]=++time; //次序從1開始,初始時由於默認將DFN[u]=LOW[u]都置為次序號
    // 將當前節點壓棧,置位在棧中,已訪問。
    visit[u]=1;
    s.push(u);
    instack[u]=1;


    //取u節點的下一路徑節點v,當沒有v可取時也說明深度搜索已經到達當前最底部,這是我們函數返回尋找另一條路徑。
    for(int j=0;j<G[u].size();j++){
        int v=G[u][j];
        if(visit[v]==0){
            tarjan(v);
            
// 在深度搜索返回時,如果v節點下存在子樹,要將u節點的LOW[u]更新。 LOW[u]=min(LOW[u],LOW[v]); } else if(instack[v]){ // v節點已經被訪問,並且在棧中,說明在當前路徑上存在環,此處只是賦值,但並不代表在u子樹的底下的多個節點沒有比當前環更大的環。無法作為深度終止條件。 LOW[u]=min(LOW[u],DFN[v]); } } int m; int num=0; //對一個環計數計數 // 在深度搜索完結後返回時,判斷DFN[u]==LOW[u],相等說明找到了一個環,將棧中節點彈出。註意tarjan算法認為單個節點也為環。 if(DFN[u]==LOW[u]){ // 將棧中節點彈出,並計數 do{ m=s.top(); s.pop(); instack[m]=0; num++; }while(m!=u); // 只有環內節點數大於兩個才是真正環。 if(num>1){ // n個點兩兩相交(互相到達),則有n*(n-1)/2條連接線 total+=num*(num-1)/2; } } }

關於為啥只用訪問一次:
開始疑惑,肯定會多條路徑通過某一點,如果用visit記錄訪問記錄的話,下一條路徑不就會不能訪問該點了嗎?遂繪制醜圖:
技術分享圖片
如圖當我們訪問到6節點時發現有環,且到達底點,這時根據算法開始返回,同時將2-6-5這條環也遍歷掉(此時5號已訪問壓棧且有LOW=1)。也就是說在返回到1號節點開始出棧時,我們已經把1號節點的子樹全部訪問了一遍,該成環的也做了標記。在1號節點下的子節點不會通向1號節點以上的節點,比如0號節點,不然1號只能算一個類似於2-6-5這條環。至於從0號到5號就不用再判斷了。所以遍歷一遍就行。我覺得巧妙之處在於在深度向前搜索過程並沒有處理數據,而在深度返回過程中開始更新數據,記錄找到的回路,並且到達子樹根節點DFN[u]==LOW[u]才開始出棧。

圖之強連通、強連通圖、強連通分量 Tarjan算法