1. 程式人生 > 實用技巧 >Tarjan演算法求強連通圖

Tarjan演算法求強連通圖

先把參考資料的傳送門放一下:

1、優質的B站視訊講解
(裡面老師給出了一個很不錯的程式碼模板)

2、tarjan求強連通分量+縮點+割點/割橋(點雙/邊雙)以及一些證明(原理簡述)


先不論縮點是怎麼做的,
根據第一個B站視訊裡的模板,
先用線性的鄰接表維護有向圖,
基本實現一下Tarjan演算法。

const int maxn = 1e5 + 10;

//線性鄰接表維護有向圖

class Node{
public:
    int nd,nxt;
    Node(){};
    Node(int nd_,int nxt_):nd(nd_),nxt(nxt_){};
};

Node G[maxn];
int head[maxn];
int cnt;

void add(int u,int v){
    G[cnt] = Node(v,head[u]);
    head[u] = cnt++;
}

void init_G(){
    memset(head,-1,sizeof(head));
    cnt = 0;
}

//下面是Tarjan演算法求強連通圖
int Low[maxn];
int Dfn[maxn];
int num = 0;//編號變數-賦值不能是0
int out[maxn];//標記被棧彈出過的元素
int Stack[maxn];//模擬棧
int cur = 0;//cur應當始終指向Stack的尾端元素

int ans = 0;

/*我們假設結點的編號從1開始*/
int dfs(int u){
    Low[u] = Dfn[u] = ++num;//結點編號
    Stack[++cur] = u;//結點入棧
    //下面開始遍歷u的臨界結點
    for(int i=head[u];~i;i=G[i].nxt){
        int v = G[i].nd;
        if(Dfn[v] == 0){//v沒有被訪問
            dfs(v);
            Low[u] = min(Low[u],Low[v]);
        }else if(!out[v]){//v已經被訪問且還在棧中
            Low[u] = Low[v];//形成環
        }
    }
    //輸出強連通分量
    if(Low[u] == Dfn[u]){
        int cur_num = 0;
        do{
            cur_num++;
            out[Stack[cur]]=1;
        }while(Stack[cur--] != u);
        //這裡做的處理是為了統計點個數大於1的連通區塊的總個數ans
        if(cur_num>1)ans++;
    }
    return 0;
}

int main(){
    frein("in.txt");
    int n,m;
    cin>>n>>m;
    init_G();
    for(int i=0;i<m;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    //由於圖並不一定是弱連通圖
    //所以這裡對每一個沒有訪問過的結點都進行搜尋
    //當然,一般這並不會讓複雜度達到O(n*n)
    for(int i=1;i<=n;i++){
        if(!Dfn[i]){
            dfs(i);
        }
    }
    cout<<ans<<endl;
    return 0;
}

OK