強連通分支及kosaraju算法

圖論中最重要的結構,很多圖論問題都可以轉化爲強連通分支來降低處理複雜度。一個強連通分支中所有的點都是互相連通的,可以將其收縮爲單個點,以此來簡化圖的處理。強連通分支中的點集合是一個最大集合,即再加入任何一個其他點都會導致不連通。

 

引理1:圖G的兩個強連通分支C、C’,如果存在點u屬於C,u’屬於C’,使得(u,u’)爲G的一條邊,則一定不存在另一條邊(v’,v),使得v’屬於C’,v屬於C。

反證法,假設存在這樣的一條邊(v’,v),則強連通分支C和C’是互相連通的,即屬於一個強連通分支,矛盾。由此可以有分支圖的概念

 

定義:圖G的分支圖GSCC=(VSCC,ESCC),將圖G的每一個強連通分支看作一個點,這個點屬於VSCC,即分支圖GSCC的點數爲圖G的強連通分支數目,不妨設G的強連通分支爲C1,C2,...,Ck,則分支圖的點集GSCC爲v1,v2,...,vk;定義邊(vi,vj)屬於ESCC,當且僅當G中有一條邊(x,y),且x屬於Ci,y屬於Cj,由引理1保證此邊的方向是唯一的。

 

引理2:圖G的分支圖GSCC是一個有向無環圖(DAG)

反證法,與引理1證明類似,假設存在一個環,則這個環上的所有的強連通分支均互相可達,與多個強連通分支矛盾。故分支圖GSCC是一個有向無環圖

 

習題22.5-1 增加一條邊對圖G的強連通分支個數會有什麼影響?

假設增加一條邊(u,v),u屬於強連通分支Ci,v屬於強連通分支Cj,

(1) 如果i==j,即同屬於一個強連通分支,則添加這條邊對強連通分支個數沒有影響;

(2) 如果Cj到Ci沒有路徑,則添加這條邊後對強連通分支個數也沒有影響

(3) 如果Cj到Ci存在一條路徑,則添加這條邊後使用Cj,Ci,...Cj形成一個環,在這個環上的所有強連通分支會變成一個新的連通分支,總的強連通分支個數會減少。

 

定義:圖G的轉置圖GT,將圖G中的所有邊的方向轉向即成GT,嚴格定義G=(V,E),GT=(V,ET), ET={(u,v):(v,u)屬於E}

 

引理3:圖GT與G有着相同的強連通分支

假設圖G的一個強連通分支Ci,圖GT中Ci對應的CiT,此子圖仍然是互相連通的,同時也是一個極大的連通子圖,否則如果存在一個點u,使得CiT 與u互相連通,則在圖G中Ci與u也是互相連通的,矛盾。

由此證明圖GT與G的強連通分支相同。

 

習題22.5-4 證明((GT)SCC)T=GSCC

由引理3GT與G的強連通分支相同,則((GT)SCC)T與GSCC有着相同的點集,只要證明其邊集也相同即可。

假設邊(x,y)屬於GSCC,即G存在兩個強連通分支Ci,Cj,x屬於Ci,y屬於Cj,則可知(y,x)必屬於(GT)SCC,於是(x,y)必屬於((GT)SCC)T

假設邊(x,y)屬於((GT)SCC)T,則可知(y,x)必屬於(GT)SCC,則(x,y)必屬於GSCC

 

 

引理4:任何一個強連通分支必定包含於圖G的DFS過程中的某一個子樹。

證明,一個強連通分支C,在圖G的DFS過程中,假設強連通分支C中第一個訪問的點是u,則C中其他點均可以從u可達,根據白色路徑定理,則C中其餘的任何點都是點u的子孫結點,如此以u爲樹根的子樹中一定包含該連通分支C

由引理4可知,從任意結點開始DFS,都會使得任何一個強連通分支必定全部包含於某一個DFS樹中,即一個DFS樹必定是由若干個強連通分支組成的。同時此引理也是強連通分支Tarjan 算法、Kosaraju的基礎

 

DFS過程中有結點的結束時間f(v),這裏定義結點集合的結束時間f(U),

f(U) = max{f(u): u屬於U},即結點集合中最後完成DFS的結點時間。

 

引理5:圖G的任意兩個強連通分支C,C’,如果存在一條邊(u,v),u屬於C,v屬於C’,則f(C) > f(C’)

證明:從C和C’這兩個集合中第一個訪問的結點來考慮

(1) 第一個訪問的結點x屬於C,則根據白色路徑定理,C和C’中其餘結點均是u的子孫結點,故結點x最後結束,顯然有f(C)> f(C’)

(2) 第一個訪問的結點x屬於C’,從x必定無法訪問到C,所以C’的其餘結點均是x的子孫結點f(C’) =f(x),當結點x訪問結點時,C中的結點尚未被訪問過,故顯然有f(C) > f(C’)

這個引理可以看作是有向無環圖中拓撲序列的一個性質,即有向無環圖存在一條邊(u,v),則必須有f(u) > f(v),只要將強連通分支收縮成一個點,即將圖G看作其分支圖GSCC

對於其轉置圖GT來講,結論正好相反,如果存在邊(u,v)屬於GT,且u、v分屬於兩個強連通分支C、C’,則有f(C) < f(C’)。

 

Kosaraju算法:第一次DFS獲取圖G的一個拓撲排序,然後按照拓撲排序的順序,對圖GT進行一次DFS,獲得的DFS森林即是不同的強連通分支

此算法需要兩次DFS,以下所有的f均針對第一次DFS過程。使用數學歸納法進行證明,假設第二次DFS時前k個DFS樹均是強連通分支,在k=0時顯然。

由歸納假設,前k個DFS樹均是強連通分支,由第二次DFS過程,取餘下所有點中的最晚結束的結點u,假設u屬於強連通分支C,則對於尚未訪問的任何一下其他連通分支C’,有f(u) = f(C) > f(C’),由引理5可知,不存在從C到C’的邊,於是從訪問u開始,DFS過程訪問完C後就結束,恰好是一個完整的強連通分支C

將G看作GSCC再來理解Kosaraju算法,第一次DFS相當於將GSCC作了一次拓撲排序,因爲GSCC是有向無環圖。然後第二次DFS時,按照GSCC的拓撲逆序對GT進行DFS,相當於以(GT)SCC逆拓撲排序的方式進行DFS,所以每一個DFS樹均對應(GT)SCC的一個點集,即G的一個強連通分支,又因爲((GT)SCC)T= GSCC,第二次DFS正好是GSCC的拓撲排序,這是Kosaraju算法的一個隱藏性質。

 

這個算法初看起來甚是神奇,簡單的兩次DFS就可以得到強連通分支,幾乎不用其他的數據結構,感謝Kosaraju這個印度人。


最後是代碼實現:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

#include "list.h"               /* list from Linux_kernel */
#include "graph.h"

static void print_scc(struct list_head *head, int scc_no)
{
    struct link_vertex *v = NULL;

    printf("The %d SCC:\n", scc_no);
    list_for_each_entry(v, head, qnode) {
        printf("%d ", v->vindex);
    }
    printf("\n");
}

int find_scc(struct link_graph *G)
{
    int *color, i = 0;
    struct link_graph GT;
    struct link_vertex *v = NULL;
    struct list_head topo_head, scc_head;
    
    color = malloc(sizeof(int) * G->vcount);
    for (i = 0;i < G->vcount;i++) {
        color[i] = COLOR_WHITE;
    }

    INIT_LIST_HEAD(&topo_head);
    graph_topo_sort(G, &topo_head);

    printf("\n\nOutput all components of this graph\n\n");
    graph_transpos(G, >);
    i = 0;
    list_for_each_entry(v, &topo_head, qnode) {
        INIT_LIST_HEAD(&scc_head);
        if (color[v->vindex] == COLOR_WHITE) {
            DFS_visit_topo(>, GT.v + v->vindex, color, &scc_head);
            print_scc(&scc_head, i++);
        }
    }
    link_graph_exit(>);
    free(color);
    printf("This graph has %d scc components\n\n", i);

    return 0;
}


發佈了72 篇原創文章 · 獲贊 28 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章