cover

基礎圖論講了最短路徑和拓撲排序。這篇進入深水區:MST、SCC、割點、二分圖。這些不是面試常客(除了 MST),但在系統設計和競賽裡會遇到。

先講結論

演算法解決什麼複雜度
Prim / Kruskal最小生成樹O(E log V) / O(E log E)
Tarjan / Kosaraju強連通分量O(V + E)
割點橋演算法找關鍵節點/邊O(V + E)
BFS 染色二分圖判定O(V + E)
匈牙利演算法二分圖最大匹配O(VE)

最小生成樹 MST

用最少的邊權重連接所有節點。想像你要鋪光纖連接所有城市——花最少的錢把大家都連上。

Kruskal:按邊權重排序,依序加入不形成環的邊。用 Union-Find 判斷是否成環。適合稀疏圖。

Prim:從一個點開始,每次加入「離已連接部分最近」的新點。用 Priority Queue 找最近的。適合稠密圖。

// Prim 的核心邏輯
PriorityQueue<int[]> pq; // (權重, 節點)
pq.add({0, 0});
 
while (!pq.isEmpty()) {
    int[] curr = pq.poll();
    if (已在 MST) continue;
    加入 MST;
    for (鄰居 v : neighbors(curr))
        if (!在 MST) pq.add({weight, v});
}

怎麼選?邊少用 Kruskal,邊多用 Prim。面試通常考 Kruskal,因為 Union-Find 是另一個考點。

強連通分量 SCC

有向圖中,任意兩點可以互達的最大子圖。

    ┌─→ 1 ─→ 2 ─┐
    │    ↖    ↓  │
    0      ↖─────┘
    └─→ 3 ─→ 4

SCC: {0,1,2}, {3}, {4}

0、1、2 之間可以互相到達,所以是一個 SCC。3 和 4 各自獨立。

Tarjan 演算法

一次 DFS 搞定。核心概念:

  • dfn[u]:u 被發現的時間
  • low[u]:u 能回溯到的最早祖先的發現時間
  • 如果 low[u] == dfn[u],u 就是一個 SCC 的根
dfn[u] = low[u] = time++;
stack.push(u);
 
for (v in neighbors) {
    if (!visited[v]) {
        dfs(v);
        low[u] = min(low[u], low[v]);
    } else if (onStack[v]) {
        low[u] = min(low[u], dfn[v]);
    }
}
 
if (low[u] == dfn[u]) {
    // 彈出 stack 直到 u → 一個 SCC
}

Tarjan 的精妙之處在於它用 stack 和 low 值,在一次 DFS 中同時完成發現和分類。但說實話,我每次重新理解 Tarjan 都要花半小時。

割點與橋

割點:移除後圖不連通的節點。:移除後圖不連通的邊。

在網路拓撲裡,割點就是「單點故障」——那台 router 掛了整個網路就斷了。找出割點,就知道哪裡需要冗餘備份。

判定條件(也是用 Tarjan 的 DFS tree):

  • 割點:子節點無法不經過 u 回到 u 的祖先(low[v] >= dfn[u]
  • 橋:v 完全無法回到 u 或更早(low[v] > dfn[u]

二分圖

節點可以分成兩組,邊只存在於組間。判定方法:BFS/DFS 染色,相鄰節點不同色。如果染色過程遇到衝突,就不是二分圖。

  A 組     B 組
  0 ─────── 1
  │         │
  2 ─────── 3

匈牙利演算法:最大匹配

在二分圖中找最多的配對。核心邏輯:

boolean dfs(int u) {
    for (int v : neighbors[u]) {
        if (!used[v]) {
            used[v] = true;
            if (match[v] == -1 || dfs(match[v])) {
                match[v] = u;
                return true;
            }
        }
    }
    return false;
}

「如果你的對象已經配對了,試著讓他的對象去找別人。」這大概是演算法裡最像社交的邏輯了。

應用場景:工作分配(人 ↔ 任務)、資源排程、甚至是穩定婚姻問題。

網路流

解決最大流問題。Ford-Fulkerson、Edmonds-Karp、Dinic 是常見的演算法。這個主題太深了,除非你在打競賽或做物流系統,否則不太會遇到。知道它存在就好。


進階圖論就像高數裡的多元微積分——學的時候覺得「這什麼鬼」,但真的用到的時候會慶幸自己學過。只是那個「用到」的機會可能比你想像的少。