??在圖論中,拓撲排序(Topological Sorting)是一個有向無環圖(DAG, Directed Acyclic Graph)的所有頂點的線性序列。且該序列必須滿足下面兩個條件:
- 每個頂點出現且只出現一次。
- 若存在一條從頂點 A 到頂點 B 的路徑,那么在序列中頂點 A 出現在頂點 B 的前面。
在有向無環圖(DAG)中才有拓撲排序,非DAG圖沒有拓撲排序一說。
算法過程
??拓撲排序的實現過程非常簡單:在每個時刻,挑選出當前入度為0的節點加入到結果序列中,并刪除當前頂點所有關聯的邊。如果當前時刻,存在多個入度為0的節點,隨機挑選一個即可。因此拓撲排序的結果可能不唯一,但是一定會滿足上述關于拓撲排序的兩個必要條件。因此算法大致過程如下:
- 初始化結果序列S為空。執行步驟2.
- 如果圖中的頂點數為0,或沒有入度為0的頂點,算法結束,返回S。否則執行步驟2.
- 挑選出當前輪次中入度為0的頂點,如果有多個,隨機挑選一個,執行步驟3.
- 將步驟2中挑選出的頂點加入到結果序列中,并刪除當前頂點和當前頂點關聯的邊。然后執行步驟1.
在DAG圖中,當算法停止時,我們可以得到當前DAG圖的一個拓撲排序結果S。下面我們用一個簡單的例子演示拓撲排序的過程。
graph
step 1
step 2
step 3
step 4
step 5
step 6
step 6
以上就是拓撲排序的全部內容,下面我們以劍指 Offer II 113. 課程順序為例,展示拓撲排序的詳細過程,代碼如下。
public int[] findOrder(int numCourses, int[][] prerequisites) {
// 存儲有向圖
List<Integer> []edges = new ArrayList[numCourses];
// 存儲每個節點的入度
int[] in_degree = new int[numCourses];
// 存儲答案
int[] result = new int[numCourses];
//初始化鄰接鏈表
for (int i = 0; i < numCourses; ++i) {
edges[i] = new ArrayList<>();
}
//構建鄰接鏈表,統計每個頂點的入度
for (int[] info : prerequisites) {
edges[info[1]].add(info[0]);
++in_degree[info[0]];
}
Queue<Integer> queue = new LinkedList<>();
// 將所有入度為 0 的節點放入隊列中
for (int i = 0; i < numCourses; ++i) {
if (in_degree[i] == 0) {
queue.offer(i);
}
}
int index = 0;
while (!queue.isEmpty()) {
// 從隊首取出一個節點
int u = queue.poll();
// 放入答案中
result[index++] = u;
for (int v: edges[u]) {
--in_degree[v];
// 如果相鄰節點 v 的入度為 0,就可以選 v 對應的課程了
if (in_degree[v] == 0) {
queue.offer(v);
}
}
}
//如果不可能完成所有課程,返回一個空數組
return index != numCourses ? new int[0] : result;
}