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

青盞發表於2017-08-20

一、解釋

在有向圖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]才開始出棧。
演算法例項:
CCF 高速公路

相關文章