拓撲排序,是對有向無回路圖進行排序,以期找到一個線性序列,這個線性序列在生活正可以表示某些事情完成的相應順序。如果說所求的圖有回路的話,則不可能找到這個序列。
在大學數據結構課上,我們知道求拓撲排序的一種方法。首先用一個入度數組保存每個頂點的入度。在進行拓撲排序時,我們需要找到入度為0的點,將其存入線性序列中,再將其從圖中刪除(與它相關的邊都刪除,相鄰的頂點的入度均減1),再重復上面的操作,直至所有的頂點都被找到為止。如果不對每次找入度為0的頂點的方法進行處理,而直接去遍歷入度數組,則該算法的時間復雜度為O(|V|2),如果使用一個隊列來保存入度為0的頂點,則可以將這個算法的復雜度降為O(V+E)。
今天在算法導論上看了用dfs來求拓撲排序的算法,才發現其高深之處,膜拜之Orz…
下面是算法導論的敘述:
本節說明了如何運用深度優先搜索,對一個有向無回路圖(dag)進行拓撲排序。對有向無回路圖G=(V,E)進行拓撲排序后,結果為該圖頂點的一個線性序列,滿足如果G包含邊(u, v),則在該序列中,u就出現在v的前面(如果圖是有回路的,就不可能存在這樣的線性序列)。一個圖的拓撲排序可以看成是圖中所有頂點沿水平線排列而成的一個序列。使得所有的有向邊均從左指向右。因此,拓撲排序不同於通常意義上的排序。
在很多應用中,有向無回路圖用於說明時間發生的先后次序,下圖1即給出一個實例,說明Bumstead教授早晨穿衣的過程。他必須先穿好某些衣服,才能再穿其他衣服(如先穿襪子后穿鞋),其他一些衣服則可以按任意次序穿戴(如襪子和褲子),在圖1中,有向邊<u,v>表示衣服u必須先於衣服v穿戴。因此,該圖的拓撲排序給出了一個穿衣的順序。圖2說明了對該圖進行拓撲排序后,將沿水平線方向形成一個頂點序列,使得圖中所有有向邊均從左指向右。

拓撲排序算法具體步驟如下:
1、 調用dfs_travel();
2、 在dfs_travel()每次調用dfs()的過程中,都記錄了頂點s的完成時間,將頂點s按完成順序保存在存放拓撲排序順序的數組topoSort[]中。這樣,該數組就存放了按先后順序訪問完成的所有頂點。
3、 最后拓撲排序得到的線性序列,即為topoSort[]的逆序。
現在我們分析一下時間復雜度,首先深度優先搜索的時間復雜度為O(V+E),而每次只需將完成訪問的頂點存入數組中,需要O(1),因而總復雜度為O(V+E)。
可能有人會問,這樣也行?別着急,現在給出它的證明:
證明:假設對某一已知有向無回路圖G=(V,E)運行dfs_travel()過程,以便確定其頂點的完成時刻。只要證明對任一對不同頂點u、v∈V,若G中存在一條從u到v的邊,則f[v]<f[u]。考慮過程dfs_travel()所探尋的任何邊(u,v),當探尋到該邊時,頂點v必然是已考察完成的頂點或者還未被訪問到的頂點。若v是還未被訪問到的頂點,則它是u的后裔,f[v]<f[u]。若v為已考察完成的頂點,則已完成探索,且f[v]已經設置了。因為仍在探尋u,還要為f[v]賦時間戳。一旦這么做后,就同樣有f[v]<f[u],這樣一來,對於有向無回路圖中任意邊(u,v),都有f[v]<f[u],從而定理得證。
簡單解釋:如果存在u到v的通路,則必然存在f[u]>f[v],即u肯定在v的前面。如果還不理解的話,讀者可能需要好好補充下深度優先搜索相關知識。
將上圖頂點轉化為數字:

代碼如下(拓撲排序順序與數組順序逆序):
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 6 #define maxn 100 //最大頂點個數 7 int n, m; //頂點數,邊數 8 9 struct arcnode //邊結點 10 { 11 int vertex; //與表頭結點相鄰的頂點編號 12 arcnode * next; //指向下一相鄰接點 13 arcnode() {} 14 arcnode(int v):vertex(v),next(NULL) {} 15 }; 16 17 struct vernode //頂點結點,為每一條鄰接表的表頭結點 18 { 19 int vex; //當前定點編號 20 arcnode * firarc; //與該頂點相連的第一個頂點組成的邊 21 }Ver[maxn]; 22 23 void Init() //建立圖的鄰接表需要先初始化,建立頂點結點 24 { 25 for(int i = 1; i <= n; i++) 26 { 27 Ver[i].vex = i; 28 Ver[i].firarc = NULL; 29 } 30 } 31 32 void Insert(int a, int b) //插入以a為起點,b為終點,無權的邊 33 { 34 arcnode * q = new arcnode(b); 35 if(Ver[a].firarc == NULL) 36 Ver[a].firarc = q; 37 else 38 { 39 arcnode * p = Ver[a].firarc; 40 while(p->next != NULL) 41 p = p->next; 42 p->next = q; 43 } 44 } 45 46 #define INF 9999 47 bool visited[maxn]; //標記頂點是否被考察,初始值為false 48 int parent[maxn]; //parent[]記錄某結點的父親結點,生成樹,初始化為-1 49 int d[maxn], time, f[maxn]; //時間time初始化為0,d[]記錄第一次被發現時,f[]記錄結束檢查時 50 int topoSort[maxn]; 51 int cnt; 52 void dfs(int s) //深度優先搜索(鄰接表實現),記錄時間戳,尋找最短路徑 53 { 54 //cout << s << " "; 55 visited[s] = true; 56 time++; 57 d[s] = time; 58 arcnode * p = Ver[s].firarc; 59 while(p != NULL) 60 { 61 if(!visited[p->vertex]) 62 { 63 parent[p->vertex] = s; 64 dfs(p->vertex); 65 } 66 p = p->next; 67 } 68 time++; 69 f[s] = time; 70 topoSort[cnt++] = s; 71 72 } 73 void dfs_travel() //遍歷所有頂點,找出所有深度優先生成樹,組成森林 74 { 75 for(int i = 1; i <= n; i++) //初始化 76 { 77 parent[i] = -1; 78 visited[i] = false; 79 } 80 time = 0; 81 for(int i = 1; i <= n; i++) //遍歷 82 if(!visited[i]) 83 dfs(i); 84 //cout << endl; 85 } 86 void topological_Sort() 87 { 88 cnt = 0; 89 dfs_travel(); 90 for(int i = cnt-1; i >= 0; i--) 91 cout << topoSort[i] << " "; 92 cout << endl; 93 } 94 int main() 95 { 96 int a, b, w; 97 cout << "Enter n and m:"; 98 cin >> n >> m; 99 Init(); 100 while(m--) 101 { 102 cin >> a >> b; //輸入起點、終點 103 Insert(a, b); //插入操作 104 } 105 topological_Sort(); 106 return 0; 107 }
