拓撲排序(Topological Sort)
0)拓撲排序
拓撲排序是對有向無圈圖的頂點的一種排序,這個排序的結果是如果存在一條vi到vj的路徑,那麼排序中vi在vj的前面。
下圖是一個有向無圈圖的例子:
在這個有向無圈圖中,1,6,5,7,4,2,3;1,6,5,7,2,4,3;這兩組都是拓撲排序,我們可以看到這兩種排序都滿足拓撲排序的要求,比如說1-4的路徑,可知1,7,4;1,6,5,4;1,6,7,4;1,6,5,7,4;這些路徑的點都按照拓撲排序的要求排列。
1) 簡單的拓撲排序演算法
下面我們介紹一個簡單的拓撲排序演算法:
a)先找到一個沒有輸入邊的點,輸出這個點,然後去掉與這個點連線的所有邊。
b)重複上面的步驟知道輸出所有的點。
這裡為了程式設計方便,我們要先定義一個叫做indegree的概念:
indegree: 頂點v包含的邊(u,v)的個數。
注意是有向圖的v包含的邊(u,v)的個數。因此對於上圖的點1,它的indegree=0,因為進入點1的邊為0,點1的三條邊全是指向外的。
所以通過引入indegree概念,上面的簡單演算法就可以用下面的方式表示
a) 查詢indegree為0的點p
b) 對所有與p鄰接的點的indegree = indegree -1;
c) 查詢indegree為0的點(p除外),然後迴圈過程
下面是簡單的拓撲排序演算法的一段虛擬碼
2) 拓撲排序的改進演算法void TopSort(Graph G) { int Num; Vertex V,W; for(Num=0;Num<NumVertex;Num++) { V = FindNewVertexOfDegreeZero(); if(V==NotVertex) { Error("Graph has a cycle!"); break; } Output[V] = Num; for each W adjacent to V Indegree[W]--; } }
上面的簡單演算法還是很簡單的,也很好理解,那麼為什麼要改進上面的演算法呢?這個主要是因為上面演算法的時間複雜度為O(|V|^2)。
首先FindNewVertexOfDegreeZero()函式因為要找到indegree=0的點,所以需要遍歷所有的點,因此時間複雜度為O(|V|)。而這個函式需要重複|V|次,因此上面的演算法的時間複雜度為O(|V|^2)。因此我們需要做一些改進,來降低執行時間。
可以看到,這裡可能能夠進行改進的就是FindNewVertexOfDegreeZero()函式。當我們刪除一個indegree=0的點後,只有與這個鄰接的點的indegree才會減一,其他的點的indegree值不變,因此當我們需要在一次FindNewVertexOfDegreeZero時,不需要遍歷所有的點,只需要遍歷部分相關連的點就可以。
為了實現上面的思想,我們把indegree=0的點放到一個box中,因此FindNewVertexOfDegreeZero()函式只需要在這個box中尋找就好了。當一個點的indegree=0時,我們就把這個點輸入到box中。
演算法:
a) 把ndegree=0的點A放到一個Queue中;
b) 把點A出隊,然後對所有的與A鄰接的點的indegree減一;
c) 把新的ndegree=0的點入隊;
d) 重複上面的步驟
虛擬碼實現:
void TopSort(Graph G)
{
Queue Q;
int Num=0;
Vertex V,W;
Q = CreateQueue(NumVertex);
MakeEmpty(Q);
while(!IsEmpty(Q))
{
V = Dequeue(Q);
TopNum[V] = ++Num;
for each W adjacent to V
if(--Indegree[W]==0)
Enqueue(W,Q);
}
if(Num!=NumVertex)
Error("Graph has a cycle!");
Free(Q);
}
這個方法相當於是以空間換時間,引入了一個Queue。