跳到主要內容

演算法

同一個問題,不同的解題思路

主題 · 拓撲排序

拓撲排序

有先後依賴的工作要排出一個合法順序:每次挑一個沒有前置條件的出來做。圖裡有環就排不出來,而這正好能用來偵測循環依賴。

課程規劃
1/20
程式設計0離散數學0線性代數0資料結構1計算機組織1演算法2作業系統2資料庫1機器學習2
剛排入可以上:先修課都排完了已排入(數字是順序)還在等/卡在環上
可以上的佇列
  1. 程式設計
  2. 離散數學
  3. 線性代數
目前的順序
(還沒有)

先數每門課有幾門先修課(小圓圈裡的數字)。沒有先修課的可以先上:程式設計、離散數學、線性代數。

已排
0 / 9
卡在環上
–
取出+減一的次數
18
亮起來的是這一步執行的程式碼
function topologicalSort(n: number, edges: [number, number][]): number[] | null {
const out: number[][] = Array.from({ length: n }, () => []);
const indegree = new Array(n).fill(0);
for (const [u, v] of edges) {
out[u].push(v);
indegree[v]++;
}
for (const list of out) list.sort((a, b) => a - b);
const queue: number[] = [];
for (let v = 0; v < n; v++) {
if (indegree[v] === 0) queue.push(v);
}
const order: number[] = [];
for (let head = 0; head < queue.length; head++) {
const u = queue[head];
order.push(u);
for (const v of out[u]) {
indegree[v]--;
if (indegree[v] === 0) queue.push(v);
}
}
return order.length === n ? order : null;
}

模型假設與範圍

  • Big O 與實測步數以頁面列出的基本操作為單位;視覺化的快照、畫圖、程式高亮和輸出成本另計,不是執行時間 benchmark。

什麼時候用

  • 一堆工作之間有「A 要在 B 之前」的關係,要排出一個做得完的順序:建置步驟、課程先修、資料管線、試算表的公式重算。
  • 要檢查有沒有循環依賴:排不完,就代表有環。
  • 想知道哪些工作可以同時做:同一輪一起變成「可以做」的,彼此沒有依賴。

和其他主題的關係

延伸閱讀
BFS 與 DFS

時間與空間複雜度(Big O)

操作平均最差
Kahn 拓撲排序
每個點進出佇列一次,每條邊減一次
O(V + E)O(V + E)
順便偵測環
排完時還有點沒排到,就有環
O(V + E)O(V + E)
每輪重新掃找可做的工作O(V² + E)O(V² + E)

空間:O(V + E),每個點的出邊清單與剩餘前置數

Big O 實測:n 變大時步數怎麼長

數的是:取出+減一的次數(n 個工作、每個約 3 個前置的隨機依賴圖)

Big On = 500n = 5,000n = 50,000成長倍數:實測(理論)
KahnO(n)1,99719,997199,997×100 (×100)

和其他做法比

邊數Kahn:佇列每輪重新掃一遍
100 個工作2973975,347
1,000 個工作2,9973,997503,497
3,000 個工作8,99711,9974,510,497

同一張隨機依賴圖(每個工作平均 3 個前置),數的是「看了幾個工作+減了幾次」。不用佇列、每輪都從頭找一個沒有前置的工作,成本隨工作數平方成長;Kahn 把「剛好歸零的」直接排進佇列,就不用再找。

真實世界裡的它

  • make、Bazel、Gradle 決定先編譯哪個模組。
  • npm、pip、apt 安裝套件時的依賴順序,以及「循環依賴」錯誤訊息。
  • Airflow、dbt 這類資料管線工具把工作畫成 DAG(Directed Acyclic Graph,有向無環圖)來排程。
  • 試算表改一格之後,依照公式的參照關係決定重算順序。

取捨與陷阱

  • 合法的順序通常不只一個:同時可以做的工作誰先誰後都對,別把某一個順序當成唯一答案。
  • 邊的方向要定清楚:「A → B」是 A 要先做,還是 A 依賴 B?反了,整個順序就倒過來。
  • 只有有向無環圖(DAG)排得出來;遇到環時要回報是哪些工作卡住,不要默默輸出一個不完整的順序。

LeetCode 練習