理論: 圖論(14):最大強連通圖算法 tarjan

最大強連通圖定義

在有向圖G中,如果兩個頂點間至少存在一條路徑,稱兩個頂點強連通(strongly connected)。如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。非強連通圖有向圖的極大強連通子圖,稱爲強連通分量(strongly connected components)。

樸素算法

根據定義我們不難想到, 對同一張圖同時進行正反兩次遍歷, 對兩次的遍歷結果取交集, 這裏得到的便是強連通圖。 在強連通圖中尋找到度數最大的圖即時最大強連通圖。 同時的正反兩次遍歷我們不難發現他的時間複雜度達到了O(N ^ 2 + M)

tarjan算法

算法思想:

用dfs遍歷G中的每個頂點,通dfn[i]表示dfs時達到頂點i的時間,low[i]表示i所能直接或間接達到時間最小的頂點。(實際操作中low[i]不一定最小,但不會影響程序的最終結果)

程序開始時,time初始化爲0,在dfs遍歷到v時,low[v]=dfn[v]=time++,
v入棧(這裏的棧不是dfs的遞歸時系統弄出來的棧)掃描一遍v所能直接達到的頂點k,如果 k沒有被訪問過那麼先dfs遍歷k,low[v]=min(low[v],low[k]);如果k在棧裏,那麼low[v]=min(low[v],dfn[k])(就是這裏使得low[v]不一定最小,但不會影響到這裏的low[v]會小於dfn[v])。掃描完所有的k以後,如果low[v]=dfn[v]時,棧裏v以及v以上的頂點全部出棧,且剛剛出棧的就是一個極大強連通分量。

大致證明過程

1.tarjan算法的基於定理:在任何深度優先搜索中,同一強連通分量內的所有頂點均在同一棵深度優先搜索樹中。也就是說,強連通分量一定是有向圖的某個深搜樹子樹。

2.dfs遍歷時,如果已經遍歷完v所能直接到達的頂點而low[v]=dfn[v],我們知道v一定能到達棧裏v上面的頂點,這些頂點的low一定小於 自己的dfn,不然就會出棧了,也不會小於dfn[v],不然low [v]一定小於dfn[v],所以棧裏v以其v以上的頂點組成的子圖是一個強連通分量,如果它不是極大強連通分量的話low[v]也一定小於dfn[v](這裏不再詳細說),所以棧裏v以其v以上的頂點組成的子圖是一個極大強連通分量。

3.因爲dfn保存的是深搜樹的節點編號, 所以棧中下方的點一定可以到達上方, 而low保存的是這個節點向後(棧的下方)指的最大距離, 也就是向後反向邊的最大距離。 我們說到棧中的節點正向可達, 擁有着一條反向邊之後, 這個棧中的這個區間的元素就成了一個環, 變成了任意兩點可達。 也就是我們所說的強連通, 又因爲tarjan算法會遍歷所有的點, 所以這裏的強連通經過不斷取最大之後, 得到的就是最大強連通分量。

時間複雜度

在這裏每個點進一次棧, 每條邊被遍歷一次。 所以總的確定執行次數時n + m。 即O(N + M).

原始模板

void tarjan(int i)
{
    int j;
    DFN[i]=LOW[i]=++Dindex;
    instack[i]=true;
    Stap[++Stop]=i;
    for (edge *e=V[i];e;e=e->next)
    {
        j=e->t;
        if (!DFN[j])
        {
            tarjan(j);
            if (LOW[j]<LOW[i])
                LOW[i]=LOW[j];
        }
        else if (instack[j] && DFN[j]<LOW[i])
            LOW[i]=DFN[j];
    }
    if (DFN[i]==LOW[i])
    {
        Bcnt++;
        do
        {
            j=Stap[Stop--];
            instack[j]=false;
            Belong[j]=Bcnt;
        }
        while (j!=i);
    }
}
void solve()
{
    int i;
    Stop=Bcnt=Dindex=0;
    memset(DFN,0,sizeof(DFN));
    for (i=1;i<=N;i++)
        if (!DFN[i])
            tarjan(i);
}

動畫演示

一下內容轉自:https://www.byvoid.com/blog/scc-tarjan/

原始有向連通圖:

這裏寫圖片描述

從節點1開始DFS,把遍歷到的節點加入棧中。搜索到節點u=6時,DFN[6]=LOW[6],找到了一個強連通分量。退棧到u=v爲止,{6}爲一個強連通分量。

這裏寫圖片描述

返回節點5,發現DFN[5]=LOW[5],退棧後{5}爲一個強連通分量。

這裏寫圖片描述

返回節點3,繼續搜索到節點4,把4加入堆棧。發現節點4向節點1有後向邊,節點1還在棧中,所以LOW[4]=1。節點6已經出棧,(4,6)是橫叉邊,返回3,(3,4)爲樹枝邊,所以LOW[3]=LOW[4]=1。

這裏寫圖片描述

繼續回到節點1,最後訪問節點2。訪問邊(2,4),4還在棧中,所以LOW[2]=DFN[4]=5。返回1後,發現DFN[1]=LOW[1],把棧中節點全部取出,組成一個連通分量{1,3,4,2}。

這裏寫圖片描述

至此,算法結束。經過該算法,求出了圖中全部的三個強連通分量{1,3,4,2},{5},{6}。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章