Tarjan演算法(強連通分量分解)

肘子zhouzi發表於2018-08-13

如果對原圖進行深度優先搜尋,由強連通分量定義已知,任何一個強連通分量是原圖的深度優先搜尋樹的子樹。那麼,只要確定每個強連通分量子樹的根,然後跟據這些根從樹的最低層,一個一個取出強連通分量即可。問題就是如何確定強連通分量的根和如何從最低層開始拿出強連通分量了。

如何確定強連通分量的根,在這裡維護兩個陣列,一個是dfn[maxn],另一個是low[maxn],其中dfn[v]表示頂點v被訪問的時間,low[v]為與頂點v鄰接的未刪除的頂點u的low[u]和low[v]的最小值(low[v]初始化未dfn[v])。這樣,在一次深度優先搜尋的回溯過程中,如果發現low[v]==dfn[v],那麼當前頂點就是一個強連通分量的根。因為如果不是強連通分量的根,那麼一定是屬於另一個強連通分量,而且它的根是當前頂點的祖宗,那麼存在包含當前頂點的到其祖宗的迴路,可知low[v]一定被更改為一個比dfn[v]更小的值。

對於如何取出強連通分量,如果當前節點為一個強連通分量的根,那麼它的強連通分量一定是以該根為根節點的子樹(剩下節點)子樹。在深度優先遍歷中維護一個堆疊,每次訪問一個新節點,就壓入堆疊。由於當前節點是這個強連通分量中最先被壓入堆疊的,那麼在當前節點以後壓入堆疊的並且仍在堆疊中的節點都屬於這個強連通分量。假設一個節點在當前節點壓入堆疊以後壓入並且還存在,同時不屬於該連通分量,那麼一定屬於另一個強連通分量,但當前節點是其根的祖宗,那麼這個強連通分量應該在此之前已經被取出。

時間複雜度分析:時間複雜度為O(n+m)


#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

const int maxn=1001;

struct edge{
    int v,next;
};
edge edges[maxn];
int head[maxn],cnt;

int dfn[maxn],low[maxn],stack[maxn],visit[maxn],tot,index;

void add(int x,int y)
{
    edges[cnt].v=y;
    edges[cnt].next=head[x];
    head[x]=cnt++;
}

void tarjan(int x)
{
    dfn[x]=low[x]=++tot;
    stack[++index]=x;
    visit[x]=1;
    for(int i=head[x];~i;i=edges[i].next){
        if(!dfn[edges[i].v]){
            tarjan(edges[i].v);
            low[x]=min(low[x],low[edges[i].v]);
        }else if(visit[edges[i].v]){
            low[x]=min(low[x],dfn[edges[i].v]);
        }
    }
    if(low[x]==dfn[x]){
        do{
            printf("%d ",stack[index]);
            visit[stack[index]]=0;
            index--;
        }while(x!=stack[index+1]);
        printf("\n");
    }
}

int main()
{
    memset(head,-1,sizeof(head));
    int n,m;
    scanf("%d%d",&n,&m);
    int u,v;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&u,&v);
        add(u,v);
    }
    tot=0,index=0;
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            tarjan(i);
        }
    }
    return 0;
}

 

相關文章