Luke a Pro

Luke Sun

Developer & Marketer

🇺🇦
EN||

并查集 (Union-Find)

| , 3 minutes reading.

并查集:数字世界的官僚

算法背后的故事:派对策划人的难题

想象你正在举办一场有 1000 位客人的大型派对。你想知道两个随机的人,Alice 和 Bob,是否属于同一个社交圈。

  • Alice 认识 Carol。
  • Carol 认识 Dave。
  • Dave 认识 Bob。

如果每次都要顺着这条链条去查,效率会非常低 (O(N)O(N))。

1964 年,Bernard Galler 和 Michael Fisher 提出了一种类似于 政党组织 的解决方案:每个人都属于一个小组,每个小组都有一个 “代表” (The Boss)

如果你问 Alice:“谁是你的老大?”,她说“Carol”。问 Carol,她说“Dave”。问 Dave,他说“是我自己”。 如果你问 Bob:“谁是你的老大?”,他最终也指向“Dave”。 既然他们的老大是同一个人,他们就在同一个圈子里。非常简单。

但他们加入了一个叫做 路径压缩 (Path Compression) 的天才设计:下一次你再问 Alice 时,她会跳过中间人,直接指着 Dave 说:“他是老大。” 这个简单的技巧将层级结构彻底扁平化,使得查询几乎是瞬间完成的。

为什么需要它?

并查集只为一件事而生:动态连通性 (Dynamic Connectivity)。

  • 社交网络: “你和 X 有 5 个共同好友”背后的连通图。
  • 图像处理: Photoshop 的“魔棒”工具使用它来选中所有颜色相同的相连像素。
  • Kruskal 算法: 在构建最小生成树(用最省钱的电缆连接城市)时,需要不断检查“这两个城市是否已经连通了?”

算法是如何“思考”的

这个算法就像一个只有两项工作的官僚:

  1. Find (询问): 它问一个节点:“你的领导是谁?” 如果这个节点指向别人,它就顺藤摸瓜往上找,直到找到那个指向自己的“大老板”。 关键优化: 在它回来的路上,它会悄悄告诉链路上的每个人:“顺便说一句,大老板是 Dave。下次别再层层汇报了,直接找他。” (路径压缩)

  2. Union (合并): 它将两个圈子合并。它找到 A 圈子的老大,对他说:“从现在起,你向 B 圈子的老大汇报。” 优化: 它总是让小圈子向大圈子汇报,以保持树的层级尽可能浅。(按秩合并)

“反阿克曼函数”的奇迹

并查集以其速度闻名。它的时间复杂度是 O(α(N))O(\alpha(N)),其中 α\alpha 是反阿克曼函数。 这是什么概念? 即使 NN 是宇宙中原子的总数,α(N)\alpha(N) 也不会超过 4。 在工程实践中,它实际上就是 常数时间 O(1)O(1)

实现参考 (Python)

class UnionFind:
    def __init__(self, size):
        # 初始化:每个人的老大都是自己
        self.parent = list(range(size))
        # Rank 用于优化合并策略(谁听谁的)
        self.rank = [1] * size

    def find(self, p):
        # 1. 路径压缩:官僚主义的捷径
        if self.parent[p] != p:
            # 递归找到大老板,并将我的直接上级改为大老板
            self.parent[p] = self.find(self.parent[p])
        return self.parent[p]

    def union(self, p, q):
        # 2. 按秩合并:小弟听大哥的
        rootP = self.find(p)
        rootQ = self.find(q)

        if rootP != rootQ:
            # 将较矮的树挂在较高的树下面
            if self.rank[rootP] > self.rank[rootQ]:
                self.parent[rootQ] = rootP
            elif self.rank[rootP] < self.rank[rootQ]:
                self.parent[rootP] = rootQ
            else:
                self.parent[rootQ] = rootP
                self.rank[rootP] += 1
            return True # 合并成功
        return False # 已经是自己人了

# 使用示例
# uf = UnionFind(10)
# uf.union(1, 2)
# uf.union(2, 5)
# print(uf.find(1) == uf.find(5)) # True

小结

并查集教会我们,管理复杂组织最好的方法就是 扁平化层级。通过赋予每个成员直接知晓“大老板”的能力,它将一团乱麻的关系网变成了一个光速查询系统。