cover

「這兩個人是不是同一個群組的?」——Union-Find 用接近 O(1) 的速度回答你。

先講結論

Union-Find 管理「分組」:合併兩組、查詢兩人是否同組。加上路徑壓縮和按秩合併,兩個操作都是 O(α(n)),α(n) 是反阿克曼函數——對任何實際的 n 都 4,等同常數。經典應用:圖的連通性、環檢測、Kruskal 最小生成樹。

核心概念

每個人一開始自成一組,用一個 parent 陣列追蹤「我的老大是誰」。同組的人最終都指向同一個根。

初始: {0} {1} {2} {3} {4}     每個人指向自己

union(1,2): {0} {1,2} {3} {4}   1 和 2 同一組了
union(3,4): {0} {1,2} {3,4}     3 和 4 同一組了
union(2,3): {0} {1,2,3,4}       整組合併

三個操作

// Find:找根節點(加路徑壓縮)
int find(int x) {
    if (parent[x] != x)
        parent[x] = find(parent[x]); // 順便把路徑壓扁
    return parent[x];
}
 
// Union:合併
void union(int x, int y) {
    int px = find(x), py = find(y);
    if (px != py) parent[px] = py;
}
 
// Connected:是否同組
boolean connected(int x, int y) {
    return find(x) == find(y);
}

就這樣。核心邏輯不到 15 行。

路徑壓縮:讓 find 越來越快

沒壓縮的話,tree 可能退化成鏈表,find 要走 O(n)。路徑壓縮在每次 find 時,把沿途所有節點直接指向根:

壓縮前:          壓縮後:
    1               1
    |             / | \
    2            2  3  4
    |
    3
    |
    4

下次再 find(4),一步就到根。整棵樹越用越扁。

按秩合併:矮的接到高的下面

if (rank[px] < rank[py]) parent[px] = py;
else if (rank[px] > rank[py]) parent[py] = px;
else { parent[py] = px; rank[px]++; }

避免把高的樹接到矮的下面,控制樹高。路徑壓縮 + 按秩合併,兩個一起用效果最好。

經典應用

環檢測:在無向圖中,加邊時如果兩端點已經 connected,代表形成了環。

for (int[] edge : edges) {
    if (connected(edge[0], edge[1])) return true; // 有環!
    union(edge[0], edge[1]);
}

Kruskal 最小生成樹:邊按權重排序,每次取最小的邊,如果兩端不在同一組就加入。Union-Find 讓「是否同組」的檢查幾乎是 O(1)。

連通分量計數:把所有邊 union 完,數有幾個不同的根就知道有幾個連通分量。

帶權重的 Union-Find:可以追蹤節點之間的比值關係。LeetCode 399「除法求值」就是這個應用——已知 a/b=2, b/c=3,求 a/c。

完整模板

class UnionFind {
    int[] parent, rank;
    int count;
 
    UnionFind(int n) {
        parent = new int[n]; rank = new int[n]; count = n;
        for (int i = 0; i < n; i++) parent[i] = i;
    }
 
    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }
 
    boolean union(int x, int y) {
        int px = find(x), py = find(y);
        if (px == py) return false;
        if (rank[px] < rank[py]) { int t = px; px = py; py = t; }
        parent[py] = px;
        if (rank[px] == rank[py]) rank[px]++;
        count--;
        return true;
    }
}

背下來,面試直接默寫。真的,Union-Find 的模板是 CP 值最高的背誦投資。


Union-Find 的設計哲學:我不在乎你們怎麼認識的,我只在乎你們最終是不是同一掛的。