1. 程式人生 > >tarjan演算法模板及其程式碼解釋

tarjan演算法模板及其程式碼解釋

首先解釋一下三個概念

強連通(strongly connected): 在一個有向圖G裡,設兩個點 a b 發現,由a有一條路可以走到b,由b又有一條路可以走到a,我們就叫這兩個頂點(a,b)強連通。


強連通圖: 如果 在一個有向圖G中,每兩個點都強連通,我們就叫這個圖,強連通圖。


強連通分量strongly connected components):在一個有向圖G中,有一個子圖,這個子圖每2個點都滿足強連通,我們就叫這個子圖叫做 強連通分量 

 

 

我們先來看一段簡要的虛擬碼

tarjan(u){

  DFN[u]=Low[u]=++Index // 為節點u設定次序編號和Low初值

  Stack.push(u)   // 將節點u壓入棧中

  for each (u, v) in E // 列舉每一條邊

    if (v is not visted) // 如果節點v未被訪問過

        tarjan(v) // 繼續向下找

        Low[u] = min(Low[u], Low[v])

    else if (v in S) // 如果節點u還在棧內

        Low[u] = min(Low[u], DFN[v])

  if (DFN[u] == Low[u]) // 如果節點u是強連通分量的根

  repeat v = S.pop  // 將v退棧,為該強連通分量中一個頂點

  print v

  until (u== v)

}

 

再來一張網上很多人用過的圖來做解釋

從1進入 DFN[1]=LOW[1]= ++index ----1
入棧 1
由1進入2 DFN[2]=LOW[2]= ++index ----2
入棧 1 2
之後由2進入3 DFN[3]=LOW[3]= ++index ----3
入棧 1 2 3
之後由3進入 6 DFN[6]=LOW[6]=++index ----4
入棧 1 2 3 6

到達6之後發現6沒有出度,遞迴完畢,同時發現DFN【6】 == LOW【6】,出棧,然後返回上一級,上一級中更新LOW【3】 = min(LOW【3】, LOW【6】),發現沒有變,然後判斷出DFN【3】 == LOW【3】,出棧,返回上一級,然後在結點2發現有出度,走結點5這一條路,結點5有倆條出度,6已經被訪問過了,不再訪問,1也被訪問過了,但是1還在棧中,於是我們更新LOW【5】 = min(LOW[5】, LOW【1】) = 1,這時候判斷LOW【5】 != DFN【5】,所以5不用出棧,直接遞迴完畢返回上一級結點2,結點2更新LOW【2】 = min(LOW[2], LOW[5]) = 1,同樣LOW【2】 != DFN【2】, 2也不用出棧,返回上一級結點1,1有一條沒有訪問的出度,所以我們訪問4,然後在節點4發現5已經訪問過但是還在棧中,於是LOW[4] = min(LOW[4], DFN[5]) = 5,判斷出DFN[4] != LOW【4】,所以不用出棧,然後返回上一級結點1,更新LOW【1】 = min(LOW[1], LOW[4]), 沒有變化,但是發現DFN[1] == LOW[1],然後棧裡面1以及1以上的結點全部出棧,tarjan演算法模擬完畢

 

貼一段targin演算法,演算法的輸入以及輸出就是上面模擬的例子

input:

6 8

1 3

1 2

2 4

3 4

3 5

4 6

4 1

5 6

output:

6

5

3 4 2 1

#include<iostream>
using namespace std;
const int maxn = 10010;
int head[maxn], DFN[maxn], LOW[maxn], Stack[maxn], vis[maxn];
int cnt = 1, n, m, a, b;

//鏈式前向星,不知道的可以百度去學一下
struct Node{
    int to;
    int w;
    int next;
}edge[2 * maxn];

void add(int u, int v, int w) {
    edge[cnt].to = v;
    edge[cnt].w = w;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

int total, index;
void tarjan(int u) {//代表第幾個點在處理。遞迴的是點。
    DFN[u] = LOW[u] = ++total;// 新進點的初始化。
    Stack[++index] = u;//入棧
    vis[u] = 1;//表示在棧裡面
    for (int i = head[u]; i != 0; i = edge[i].next) {
        int v = edge[i].to;
        if (!DFN[v]) {//如果沒有訪問過
            tarjan(v);//遞迴訪問
            LOW[u] = min(LOW[u], LOW[v]);//找最小父根
        } else if (vis[v]) {//如果訪問過,並且還在棧裡面
            LOW[u] = min(LOW[u], DFN[v]);//找最小父根
        }
    }
    if (DFN[u] == LOW[u]) {//發現是父根
        do{
            cout << Stack[index] << " ";
            vis[Stack[index]] = 0;
            index--;
        } while (Stack[index + 1] != u);//邊出棧邊輸出
        cout << endl;
    }
}

int main() {
    cin >> n>> m;
    for (int i = 0; i < m; i++) {
        cin >> a >> b;
        add(a, b, 0);
    }
    for (int i = 1; i <= n; i++) {
        if (!DFN[i]) {
            tarjan(i);//當這個點沒有訪問過,就從此點開始。防止圖沒走完
        }
    }
    return 0;
}

整個tarjan演算法的流程大致就是這樣,可能寫的有點簡陋,不過要是直接看懂虛擬碼之後再去理解文字再結合C++程式碼應該比較好理解,完畢。