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