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

Kosaraju

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

強連通份量:

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

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

 

Kosaraju的求解方法

對於一個無向圖的連通份量,從連通份量的任意一個頂點開始進行一次DFS,必定是能夠遍歷這個連通份量的全部定點的。因此,整個圖的連通份量數就等價於咱們對於這個圖找了幾回起點(也就是咱們遍歷這個圖了幾回DFS)。在這其中咱們每一次遍歷中所獲得的定點屬於同一個連通份量。code

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

咱們爲了求得這個圖的強聯通份量,咱們就須要對其進行DFS遍歷,而順序正遍歷的DFS的過程是顯然的,咱們仍須要一種遍歷順序來知足能夠達到咱們可使得每個強聯通份量均可以被遍歷到且遍歷的順序是有序的算法。blog

逆後序遍歷:

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

Kosaraju的步驟過程

對於任意的兩個強聯通份量之間是不可能存在有兩條路互相鏈接造成環的(這是顯然的,由於若是有環咱們即須要將其當作是同一個強聯通份量)。class

因此求解的步驟能夠分爲如下兩步:搜索

第一步:

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

第二步:

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

 

證實算法的正確性:

假設這一個圖是須要求解強聯通份量的圖

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

一共有兩種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--;
    }
}
相關文章
相關標籤/搜索