1. 程式人生 > >拓撲排序

拓撲排序

i++ nbsp 分享 方案 emp obi res bcb 選修課

概述

對一個 有向無環圖(Directed Acyclic Graph, DAG) G 進行拓撲排序,是將 G 中的所有頂點排成一個線性序列,

使得圖中任意一對頂點 u 和 v,若邊 <u,v>E(G),則 u 在線性序列中出現在 v 之前。

通常,這樣的線性序列稱為滿足拓撲次序(Topological Order) 的序列,簡稱 拓撲序列。簡單的說,由某個集合上的一個偏序得到該集合上的一個全序,這個操作稱之為 拓撲排序。

技術分享

例如,在計蒜客平臺上選擇軟件工程方向,我們會給出一個專業方案列表,如上圖。

你需要根據專業方案學習列表上的必修課程和選修課程,而學習每門課程的先決條件是學習完它的全部先修課程。

如學習《數據結構》課程就必須安排在它的先修課程《離散結構》之後。

而學習《C 語言程序設計》課程可以隨時安排,因為它是基礎課程,沒有先修課。

一個符合要求的學習課程的順序就是對這些課程進行拓撲排序。

算法流程

拓撲排序算法主要由以下兩步循環執行,直到不存在入度為 0 的頂點為止。

  1. 選擇一個入度為 0 的頂點並將它輸出;
  2. 刪除圖中從頂點連出的所有邊。

循環結束,若輸出的頂點數小於圖中的頂點數,則表示該圖中存在回路,也就是無法進行拓撲排序;否則輸出的頂點序列就是一個拓撲序列。

接下來,我們用一個例子來說明這個算法過程。

對於如下的圖,我們首先算出所有頂點的入度,並找出其中所有入度為零的頂點,發現只有 0,於是我們將 0 插入隊列中。

圖中圓圈內的數字表示頂點的入度,圓圈下方的數字表示頂點編號,直線表示邊,直線一端的箭頭表示邊的方向。

圖的下方是一個隊列,用來在拓撲排序時儲存所有未處理的入度為零的頂點。

技術分享

枚舉 0 連出的所有邊,對於每條邊,將另一端的頂點的入度減一。發現有新的入度為零的點 2,則將它們都插入到隊列中。

技術分享

處理完成後將隊首元素出隊,繼續訪問當前的隊首元素 2。

技術分享

枚舉 2 連出的所有邊,對於每條邊,將另一端的頂點的入度減一。這時發現頂點 1 和頂點 4 的入度均為零,將它們都插入隊列。

技術分享

處理完成後將隊首元素出隊,繼續訪問當前的隊首元素 1。

技術分享

枚舉 1 連出的所有邊,修改對應的入度。

技術分享

1 出隊,繼續訪問當前的隊首元素 4,調整對應頂點的入度。發現頂點 3 的入度為零,將它插入隊列中。

技術分享

4 出隊,目前隊首頂點為 3,發現沒有和它相鄰的頂點,將其出隊。此時隊列為空,算法結束。

我們發現,這 5 個頂點已經都訪問過了,所以最終頂點入隊的順序就是這張有向圖的一個拓撲排序。

技術分享

基於鄰接表的 C++ 示例代碼如下:

struct edge {
    int v, next;
} e[MAX_M];

int p[MAX_N], eid;
int topo() {
    queue<int> q;
    for (int i = 1; i <= n; i++) {
          if (indegree[i] == 0) {  // 將所有入度為零的頂點入隊
            q.push(i);
        }
    }
    while (!q.empty()) {
        int now = q.front();
        cout << "visiting " << now << endl;
        q.pop();
        for (int i = p[now]; i != -1; i = e[i].next) {
            int v = e[i].v;
            indegree[v]--;
            if (indegree[v] == 0) {  // 將入度新變成零的頂點入隊
                q.push(v);
            }
        }
    }
}

例題:字母框

將若幹個由字母組成的矩形疊加到一起,給定最終的疊加效果,求出一個合法的疊放次序。保證每個矩形只由一種字母組成,寬度為 11,且不同矩形對應的字母各不相同。

........ ........ ........ ........ .CCC....
EEEEEE.. ........ ........ ..BBBB.. .C.C....
E....E.. DDDDDD.. ........ ..B..B.. .C.C....
E....E.. D....D.. ........ ..B..B.. .CCC....
E....E.. D....D.. ....AAAA ..B..B.. ........
E....E.. D....D.. ....A..A ..BBBB.. ........
E....E.. DDDDDD.. ....A..A ........ ........
E....E.. ........ ....AAAA ........ ........
EEEEEE.. ........ ........ ........ ........
    1        2        3        4        5

例如,對於上面這 5 個矩形,有如下的最終疊放效果:

.CCC....
ECBCBB..
DCBCDB..
DCCC.B..
D.B.ABAA
D.BBBB.A
DDDDAD.A
E...AAAA
EEEEEE..

保證每個矩形的四條邊上最終都至少出現一個點。

解法:拓撲排序題目的關鍵,在於建立實際問題和圖中有向邊的對應關系。

由於題目保證,我們可以獲得每個矩形四條邊在圖中占據的位置。

如果兩個矩形有公共點,例如矩形AB,它們的公共點最終顯示的是B,因此B一定在A之後放入。

我們就可以從A點向B點連一條有向邊。最終建立的有向圖如下所示:

技術分享

對於這張有向圖,存在合法拓撲序列:E,D,A,B,C,這也就是合法的放入順序。

拓撲排序