Tarjan學習筆記

從頭開始學習OI之Tarjan. 今天從新學習了Tarjan算法..來這裏寫一下學習筆記...node

用處

Tarjan算法,是一個關於圖的聯通性的神奇算法.基於DFS(深度優先搜索).是對於有向圖的算法是.根據樹,棧,打標記等方法來完成剖析一個圖的工做.算法


咱們先來學習一下Tarjan算法須要知道的定義:
強連通,強連通圖,強連通份量數組

強連通(Strongly Connected):

在一個有向圖 G 裏,有兩個點 a,b ,由a有一條路能夠走到b,由b又有一條路能夠走到a,咱們就叫這兩個頂點 (a,b) 強連通.學習

強連通圖(Strongly Connected Graph):

若是在一個有向圖 G 中,每兩個點都強連通,咱們就叫這個圖 強連通圖spa

強連通份量(Strongly Connected Components):

在一個有向圖 G 中,有一個子圖,這個子圖每2個點都知足強連通,咱們就叫這個子圖叫作 強連通份量 (如圖中的 1,2,3 組成的份量就叫強連通份量)
code


Tarjan算法:

Tarjan所作的事情很簡單...就是找到每個強連通份量(單獨一個點也算是強連通份量..) 在實現算法以前...咱們先定義幾個數組:
dfn[i] 表示第 i 個節點的時間戳..也就是在DFS這張圖的時候這個點被遍歷到的時刻..
low[i] 表示第 i 個節點所在的強連通份量的根節點(按理說強連通份量無所謂根節點..這裏的根節點是指在那一棵DFS樹中這個強連通份量的根節點)
component

很明顯若是 dfn[i] == low[i] 那麼就表明這一個點是一個強連通份量的根
先看一下算法流程
blog

void Tarjan(int u) {
    dfn[u] = low[u] = ++Index; //將dfn和low都初始化爲時間戳,也就是dfs到的時刻
    Stack[++top] = u; //將該點壓入dfs棧中
    vis[u] = 1; //標記點在棧中
    for(int i = head[u]; i; i = edge[i].next) { //DFS過程
        if(!dfn[edge[i].to]) { //若是該點沒有被搜索到過
            Tarjan(edge[i].to); //對於該點進行算法(即dfs的過程)
            low[u] = min(low[u],low[edge[i].to]); //搜索完成返回時更新一下這個強連通份量裏的全部點的在dfs樹中的根節點
        } else if(vis[edge[i].to]) { //若是該點已經在搜索棧中,那麼表明當前棧中在這個點後的點在一個強連通份量裏,那麼這個搜索到的點就是這個強連通份量的根節點..
            low[u] = min(low[u],dfn[edge[i].to]); //將當前點所在強連通份量的根節點修改成搜索到的這個節點,也就是根節點..
        }
    }
    if(dfn[u] == low[u]) { //按照上面的定義咱們知道這是判斷是不是一個強連通份量的根節點
        while(Stack[top] != u) { //按照上面所說的將該點在棧後的全部節點都彈出(在一個強連通份量裏..也就是咱們要求的了..能夠染色存儲起來備用或者縮成一個點(縮點算法))
            printf("%d ",Stack[top]);
            vis[Stack[top--]] = 0;
        }
        printf("%d\n",Stack[top]); //彈出當前的這個根
        vis[Stack[top--]] = 0;
    }
}

首先來一張有向圖 \(G\).咱們一點一點來模擬整個算法.
遞歸




首先是一點一點的入棧..也就是上面DFS遍歷的時候的順序..叫作DFS序或者入棧序..即:get


Step1: 1號點入棧 dfn[1] = low[1] = ++index (1)
此時棧爲: 1
Step2: 2號點入棧 dfn[2] = low[2] = ++index (2)
此時棧爲: 1 2
Step3: 3號點入棧 dfn[3] = low[3] = ++index (3)
此時棧爲: 1 2 3
Step4: 6號點入棧 dfn[6] = low[6] = ++index (4)
此時棧爲: 1 2 3 6

左邊這張樹的圖就是當前操做完成後的DFS樹...
走到這裏以後咱們看到右邊圖中六號節點沒有了出邊...也就是上面說的第一步或者說是第一個判斷結束了...
咱們就要開始返回了,明顯 low[6] == dfn[6] 因此6就是一個強連通份量的根節點;
棧要一直彈出直到彈出6號節點 此時棧爲: 1 2 3
而後返回到三號..三號再無出邊..
也一直彈出直到將其彈出 存爲一個強連通份量的根 此時棧爲: 1 2


發現2號節點還有能夠繼續遍歷下去的邊..因而將五號節點壓入棧中即:
dfn[5] = dfn[5] = ++index(5) 此時棧爲: 1 2 5
再遍歷發現一個6號..已經遍歷過就不在管他了..
再遍歷發現一個1號節點..1號在棧中..因而進入第二個if語句,修改
low[5] = min(lowhttp://www.javashuo.com/tag/5,lowhttp://www.javashuo.com/tag/1) 因此 low[5] 也就是五號節點所在的強連通份量的根就是1
五號沒有出邊了..返回上一層..修改 low[2] = min(lowhttp://www.javashuo.com/tag/2,low5),low[2] = 1
二號沒有出邊了..返回上一層..修改 low[1] = min(lowhttp://www.javashuo.com/tag/1,low2),low[1] = 1 low[1]依然等於1
一號還有出邊..遍歷到四號 dfn[4] = low[4] = ++index(6) 此時棧爲 1 2 5 4
四號遍歷到五號..五號在棧中因此更新一下 low[4] = min(low4,low5),low[4] = 1;
再返回 low[1] = min(lowhttp://www.javashuo.com/tag/1,low4) ,low[1] = 1;
而後1號也沒有出邊了..這時棧一直彈出直到將1號彈出 棧空

最後的DFS樹是這樣的

按照咱們找到的根節點拆成一個個的強連通份量

這樣就完成了

咱們把以一號爲開始的連通圖都遍歷一遍了...爲了防止圖有多個(不連通) 咱們要在調用Tarjan的時候這樣寫

for(int i = 1; i <= n; ++i) 
  if(!dfn[i]) Tarjan(i); //若是沒有時間戳,那就表明沒有遍歷到,今後點開始Tarjan

這樣就能夠把全部的圖都給遍歷一遍了...




來一道裸題。
輸入:
一個圖有向圖。
輸出:
它每一個強連通份量。

這個圖就是剛纔講的那個圖。如出一轍。

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 <cstdio>
#include <algorithm>

using namespace std;

struct node {
    int to,next;
} edge[1001];

int cnt,Index,top;
int dfn[1001],low[1001];
int stack[1001],head[1001],visit[1001];

void add(int x,int y) {
    edge[++cnt].next = head[x];
    edge[cnt].to = y;
    head[x] = cnt;
}
void tarjan(int x) { //表明第幾個點在處理。遞歸的是點。
    dfn[x] = low[x] = ++Index; // 新進點的初始化。
    stack[++top] = x; //進棧 
    visit[x] = 1; //表示在棧裏
    for(int i = head[x]; i; i = edge[i].next) {
        if(!dfn[edge[i].to]) { //若是沒訪問過
            tarjan(edge[i].to); //往下進行延伸,開始遞歸
            low[x] = min(low[x],low[edge[i].to]);//遞歸出來,比較誰是誰的兒子/父親,就是樹的對應關係,涉及到強連通份量子樹最小根的事情。
        } else if(visit[edge[i].to]) { //若是訪問過,而且還在棧裏。
            low[x] = min(low[x],dfn[edge[i].to]);//比較誰是誰的兒子/父親。就是連接對應關係
        }
    }
    if(low[x] == dfn[x]) { //發現是整個強連通份量子樹裏的最小根。
        do {
            printf("%d ",stack[top]);
            visit[stack[top]] = 0;
            top--;
        } while(x != stack[top+1]);//出棧,而且輸出。
        printf("\n");
    }
    return ;
}
int main() {
    int n,m;
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i = 1; i <= m; i++) {
        scanf("%d%d",&x,&y);
        add(x,y);
    }
    for(int i = 1; i <= n; i++)
        if(!dfn[i]) tarjan(i);//當這個點沒有訪問過,就今後點開始。防止圖沒走完
    return 0;
}
相關文章
相關標籤/搜索