最大流與二分匹配
一個管線網路最多能送多少水?Ford-Fulkerson 不斷找還有剩餘容量的路徑;同一套方法也能解工作分配這類二分匹配問題。
網路
邊上顯示
1/8
增廣路徑逆著邊走:把流量退回最小割(虛線)割的起點這一側
每條邊寫的是「流量/容量」,粗體代表已經滿了。
在剩餘圖上做 BFS(Breadth-First Search,廣度優先搜尋),找到邊數最少的路:s → a → c → t。最窄的一段還剩 12,所以可以再送 12。
總流量
0
增廣次數
0
最小割容量
…
亮起來的是這一步執行的程式碼
// Returns the maximum flow and the source side of a minimum cut.function edmondsKarp(cap: number[][], s: number, t: number): { flow: number; cut: number[] } { const n = cap.length; const flow = cap.map((row) => row.map(() => 0)); let total = 0; for (;;) { const parent = new Array(n).fill(-1); parent[s] = s; const queue = [s]; while (queue.length && parent[t] === -1) { const u = queue.shift()!; for (let v = 0; v < n; v++) { if (parent[v] === -1 && cap[u][v] - flow[u][v] > 0) { parent[v] = u; if (v === t) break; queue.push(v); } } } if (parent[t] === -1) { // No path left: what is still reachable is the source side of a minimum cut. const cut = parent.flatMap((p, v) => (p === -1 ? [] : [v])); return { flow: total, cut }; } let bottleneck = Infinity; for (let v = t; v !== s; v = parent[v]) bottleneck = Math.min(bottleneck, cap[parent[v]][v] - flow[parent[v]][v]); for (let v = t; v !== s; v = parent[v]) { flow[parent[v]][v] += bottleneck; flow[v][parent[v]] -= bottleneck; } total += bottleneck; }}模型假設與範圍
- Big O 與實測步數以頁面列出的基本操作為單位;視覺化的快照、畫圖、程式高亮和輸出成本另計,不是執行時間 benchmark。
什麼時候用
- 有容量限制的網路,問「最多能送多少」:水管、頻寬、道路、物流。
- 二分配對:兩群東西兩兩配對(工人和工作、學生和學校),加上起點和終點、容量全設 1,最大流量就是最多能配幾對。
- 最小割:要找「切斷哪些邊花費最少就能把兩邊分開」,例如網路最脆弱的地方、影像分割的前景背景。
和其他主題的關係
時間與空間複雜度(Big O)
| 操作 | 平均 | 最差 |
|---|---|---|
| Edmonds-Karp 最多 O(VE) 次增廣,每次一趟 BFS O(E);實際上通常少很多 | O(VE²) | O(VE²) |
| 容量都是整數時 f 是最大流量:每次至少多 1 | O(E · f) | O(E · f) |
| 二分配對(Hopcroft-Karp) | O(E√V) | O(E√V) |
空間:O(V + E),剩餘容量和 BFS 的父節點;示範用的是 O(V²) 的矩陣
Big O 實測:n 變大時步數怎麼長
數的是:增廣次數(n 個工人、n 份工作,每人會 3 份)
| Big O | n = 10 | n = 20 | n = 40 | n = 80 | 成長倍數:實測(理論) | |
|---|---|---|---|---|---|---|
| Edmonds-Karp:增廣次數 | O(n) | 10 | 19 | 40 | 77 | ×7.7 (×8.0) |
容量都是 1,每次增廣只多配一對,次數就跟配對數一樣隨 n 線性增長;每次 BFS 本身的成本也跟著變大,總工作量見上方表格。
和其他做法比
| 邊數 | 最大流量 | 增廣次數(BFS 次數 − 1) | BFS 檢查的格子 | |
|---|---|---|---|---|
| 水管網路(示範) | 9 | 23 | 3 | 102 |
| 配對:10 個工人、10 份工作 | 50 | 10 | 10 | 2,222 |
| 配對:20 個工人、20 份工作 | 100 | 19 | 19 | 15,582 |
| 配對:40 個工人、40 份工作 | 200 | 40 | 40 | 93,972 |
| 配對:80 個工人、80 份工作 | 400 | 77 | 77 | 757,998 |
配對問題裡每個工人會做隨機 3 份工作,容量全是 1,所以每條增廣路徑只多配一對:增廣次數等於配到的對數,跟 n 成正比。每次 BFS 掃的是 (2n + 2) × (2n + 2) 的容量矩陣,所以總工作量大約是 n 的三次方:n 變 8 倍、格子變三百多倍。改用鄰接串列,一次 BFS 是 O(V + E);Hopcroft-Karp 一次 BFS 找好幾條路,配對可以做到 O(E√V)。
真實世界裡的它
- 航空公司排機組人員、醫院排班、把廣告分配到版位。
- 電腦視覺的影像分割(graph cut):像素是點,最小割把前景和背景切開。
- 棒球淘汰問題:某隊就算剩下的比賽全勝,還有沒有機會拿第一?可以化成一個流量問題。
取捨與陷阱
- 忘了反向邊:只沿著原本的方向找路,會卡在第一次選錯的路線上,得到比最大值小的答案。剩餘圖的反向邊讓演算法能「反悔」。
- 用 DFS 隨便找一條增廣路(Ford-Fulkerson 原始版):容量很大時次數可能和容量成正比,容量是無理數時甚至不會停。用 BFS 找最短的路就保證 O(VE²)。
- 最小割要在剩餘圖上找起點走得到的點,不是在原圖上;而且割的容量只算從起點這側出去的邊。