连通性

并查集(Union-Find)详解:附代码

并查集回答一个看似简单的问题:这两样东西连通吗?它以近乎常数的时间做到这一点,并且是 Kruskal 算法、环检测和无数面试题背后默默运转的引擎。下面讲它如何运作,以及为什么如此快。

11 分钟阅读 更新时间:2026 年 7 月 适合初学者
Mohammed Islam Hadjoudj
Mohammed Islam Hadjoudj
Expert Operations Research Engineer

什么是并查集?

并查集,也称不相交集合并(DSU),是一种数据结构,用来跟踪被划分为互不重叠分组的一批元素。每个元素恰好属于一个组,每个组由唯一的代表元(它的根)来标识。

诀窍在于分组的存储方式:作为一片树的森林。每个元素持有一个指向父节点的指针,沿着父节点一路向上总会到达该组的根。两个元素在同一组当且仅当它们共享同一个根。这就是全部思想,其余一切都是为了让它变快。

两个操作

并查集恰好支持两个操作,它的全部名声都系于把这两件事几乎瞬间完成。

由于分组只会合并、从不拆分,并查集非常适合那些连接随时间加入、却从不移除的问题。

朴素版本及其问题

第一次尝试只是存储父指针,通过让一个根指向另一个根来合并。它能用,但有一个糟糕的失效模式:没有什么能阻止树长成长链。如果每次 union 都把一个节点摞在上一个之上,find 就得走一条长度为 n 的链,每次操作退化到 O(n)

那并不比一个普通列表更好。解决办法是两处小改动,它们合在一起是数据结构中最著名的成果之一。

改变一切的两个优化

按秩合并让树保持浅。合并两个组时,总是把较矮的树挂到较高那棵树的根下。矮树挂在高树上不会增加高度,所以树保持扁平。

路径压缩边走边压平。每次 find 向上走到根时,它把途经的每个节点都直接指向那个根。之后对其中任意一个再做 find 就只是一跳。下图展示了一次 find 把一条链坍缩。

Before: find(4) After: path compression 1 2 3 4 1 2 3 4
一次 find(4) 向上走到根 1,然后把途经的每个节点都直接指向 1。链变成了一棵扁平的树。
按秩合并与路径压缩一起使用,能让每棵树几乎完全扁平,因此两个操作都以近乎常数的时间运行。单用其一有帮助;两者合用才是并查集出名的原因。

Python 实现

整个结构装进一个小类。两个数组完成全部工作:parentrank

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))   # 每个元素起初都是自己的根
        self.rank = [0] * n            # 每棵树高度的一个上界

    def find(self, x):
        # 路径压缩:把 x 直接指向根。
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def union(self, a, b):
        ra, rb = self.find(a), self.find(b)
        if ra == rb:
            return False               # 已经在同一组

        # 按秩合并:把较矮的树挂到较高的树下。
        if self.rank[ra] < self.rank[rb]:
            ra, rb = rb, ra
        self.parent[rb] = ra
        if self.rank[ra] == self.rank[rb]:
            self.rank[ra] += 1
        return True

注意,当两个元素本已连通时,union 返回 False。正是这一个布尔值让环检测和 Kruskal 算法如此干净:如果一次 union 失败,你正要加入的那条边就会闭合一个环。

复杂度:几乎常数

同时使用两个优化时,对 n 个元素做 m 次操作的总时间为 O(m · α(n)),其中 α反阿克曼函数

版本每次操作说明
朴素O(n)树可能退化成链
仅按秩合并O(log n)树保持平衡
仅路径压缩O(log n) 摊还随时间压平
两者合用O(α(n)) 摊还实际上常数

反阿克曼函数增长得如此之慢,以至于对任何塞得进可观测宇宙的 nα(n) 至多为 4。实践中,把每次操作当作常数时间即可。要看它在所有图算法中的位置,见复杂度指南速查表

它用在哪里

凡是需要在连通性不断增长时对其进行跟踪的地方,都会出现并查集。

它还位于图论学习路线图的第 3 阶段,正是你学习生成树的地方。

实时看着分组合并

当你看到两棵树合并、一条路径坍缩时,并查集就豁然开朗。在实时图上的 Kruskal 算法中探索它。

打开算法可视化工具

常见问题

并查集用来做什么?

并查集,也称不相交集合并(DSU),跟踪一批被划分为互不重叠分组的元素。它能快速回答两个问题:这两个元素是否在同一组,以及合并两个元素所在的分组。它支撑着连通性查询、环检测,以及 Kruskal 最小生成树。

并查集的时间复杂度是多少?

同时使用路径压缩和按秩合并时,每次 find 或 union 的摊还时间为 O(alpha(n)),其中 alpha 是反阿克曼函数。对你会遇到的任何输入,alpha(n) 至多为 4,因此每次操作实际上就是常数时间。

按秩合并和路径压缩有什么区别?

按秩合并在合并时总是把较矮的树挂到较高的树下,从而让树保持浅。路径压缩在 find 过程中把访问过的每个节点直接指向根,从而把树压平。两者一起使用,每次操作就近乎常数时间。

并查集在图中用在哪里?

经典用途包括:Kruskal 最小生成树算法、在无向图中检测环、统计连通分量,以及任何随时间加边的动态连通性问题。

进一步的学习资源

看见它,而不只是读它

当你看到 Kruskal 算法用并查集一条边一条边地构建生成树时,它最令人信服。加载一张图,按下播放。

使用算法可视化工具进行练习