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


原文地址: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]才開始出棧。 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM