Kosaraju與Tarjan(圖的強連通分量)

Kosaraju

這個算法是用來求解圖的強連通分量的,這個是圖論的一些知識,前段時間沒有學,這幾天在補坑...

強連通分量:

有向圖中,儘可能多的若干頂點組成的子圖中,這些頂點都是相互可到達的,則這些頂點成爲一個強連通分量

如下圖所示,a、b、e以及f、g和c、d、h各自構成一個強聯通分量

 

Kosaraju的求解方法

對於一個無向圖的連通分量,從連通分量的任意一個頂點開始進行一次DFS,一定是可以遍歷這個連通分量的所有定點的。所以,整個圖的連通分量數就等價於我們對於這個圖找了幾次起點(也就是我們遍歷這個圖了幾次DFS)。在這其中我們每一次遍歷中所得到的定點屬於同一個連通分量。

我們從無向圖來推向有向圖:

我們爲了求得這個圖的強聯通分量,我們就需要對其進行DFS遍歷,而順序正遍歷的DFS的過程是顯然的,我們仍需要一種遍歷順序來滿足可以達到我們可以使得每一個強聯通分量都可以被遍歷到且遍歷的順序是有序的算法。

逆後序遍歷:

DFS的逆後序遍歷指的是假如到達了A節點且A節點並沒有被訪問過,就去遍歷與A節點相連的且沒有被訪問的其他節點,然後將這些節點假如棧中,最後這個棧從棧頂到棧底的順序DFS逆後序遍歷。

Kosaraju的步驟過程

對於任意的兩個強聯通分量之間是不可能存在有兩條路互相連接形成環的(這是顯然的,因爲如果有環我們即需要將其看成是同一個強聯通分量)。

所以求解的步驟可以分爲以下兩步:

第一步:

對原圖取反,從任意一個頂點開始對反向圖進行逆後續DFS遍歷

第二步:

按照逆後續遍歷中棧中的頂點出棧順序,對原圖進行DFS遍歷,一次DFS遍歷中訪問的所有頂點都屬於同一強連通分量。

 

證明算法的正確性:

假設這一個圖是需要求解強聯通分量的圖

那麼對於這個圖進行取反就得到了這個圖:

一共有兩種DFS的可能性:

從A點開始:

假設DFS從位於強連通分量A中的任意一個節點開始。那麼第一次DFS完成後,棧中全部都是強連通分量A的頂點,第二次DFS完成後,棧頂一定是強連通分量B的頂點。

從B點開始:

假設DFS從位於強連通分量B中的任意一個頂點開始。顯然我們只需要進行一次DFS就可以遍歷整個圖,由於是逆後續遍歷,那麼起始頂點一定最後完成,所以棧頂的頂點一定是強連通分量B中的頂點。

所以對於每一次DFS,都會有一個對應的強聯通分量,證畢

Code:

 

void dfsone(int x)
{
    vst[x] = 1;
    for(int i=1;i<=n;i++)
    if(!vst[i] && map[x][i])
    dfsone(i);
    d[++t] = x;  //最後訪問的節點 
}
  //d[i] = x : i -> 組  x -> 節點 

void dfstwo(int x)
{
    vst[x] = t;
    for(int i=1;i<=n;i++)
    if(!vst[i] && map[i][x])
    dfstwo(i);
}

void kosaraju()
{
    int t = 0;
    for(int i=1;i<=n;i++)
    if(!vst[i])
    dfsone(i);
    memset(vst,0,sizeof(vst));
    t = 0;
    for(int i=n;i>=1;i--)
    if(!vst[d[i]])
    {
        t++;
        dfstwo(d[i]);
    }
}

 

 

以上就是Kosaraju

接下來開始Tarjan

 

Tarjan

Tarjan是一種基於DFS的算法 ,圖中每個強連通分量爲搜索樹的一棵子樹

我們在DFS的過程中會遇到四種邊:

樹枝邊:一條經過的邊,即DFS搜索樹上的一條邊

前向邊:與DFS方向一致,從某個節點指向其子孫的邊

後向邊:與DFS方向相反,從某個節點指向其祖先的邊
橫叉邊:從某個節點指向搜索樹中另一子樹的某節點的邊

定義一下:

DFN[i]:在DFS中該節點被搜索的次序(時間戳)

LOW[i]:爲i或i的子樹能夠追溯到的最早的棧中節點的次序號

那麼我們就可以顯然地得到:

如果(u,v)爲樹枝邊,u爲v的父節點,則 LOW[u] = min(LOW[u],LOW[v])

如果(u,v)爲後向邊或者是指向棧中節點的橫叉邊,則 LOW[u] = min(LOW[u],DFN[v])

當節點u的搜索過程結束後,如果當DFN[ i ]==LOW[ i ]時,爲i或i的子樹可以構成一個強連通分量。

算法過程:

以1爲Tarjan 算法的起始點,如圖

順次DFS搜到節點6

 回溯時發現LOW[ 5 ]==DFN[ 5 ] ,  LOW[ 6 ]==DFN[ 6 ] ,則{ 5 } , { 6 } 爲兩個強連通分量。回溯至3節點,拓展節點4.

拓展節點1 , 發現1再棧中更新LOW[ 4 ],LOW[ 3 ] 的值爲1

 回溯節點1,拓展節點2

自此,Tarjan Algorithm 結束,{1 , 2 , 3 , 4 } , { 5 } ,  { 6 } 爲圖中的三個強連通分量。

不難發現,Tarjan Algorithm 的時間複雜度爲O(E+V).

Code:

void tarjan(int x)
{
    dfn[u] = low[u] = ++num;
    st[++top] = u;
    for(int i=fir[u];i;i=nex[i])
    {
        int v = to[i];
        if(!dfn[i])
        {
            tarjan(v);
            low[u] = min(low[u],low[v]);
        }
        else
        if(! co[v])
        low[u] = min(low[u],dfn[v]);
    }
    if(low[u] == dfn[u])
    {
        co[u] = ++col;
        while(st[top] != u)
        {
            co[st[top]] = col;
            top--;
        }
        top--;
    }
}

 

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