相关笔记

概览

本节介绍 不相交集合数据结构(也称为 并查集,disjoint-set data structure),它维护一个不相交动态集合的划分 ,支持三种核心操作:MAKE-SET 创建单元素集合,FIND-SET 查询元素所属集合的代表,UNION 合并两个集合。本节通过 无向图连通分量 的经典应用展示其用法,并建立分析框架:设 为 MAKE-SET 操作次数, 为总操作次数,后续各节将逐步优化至接近 的运行时间。


知识结构总览

flowchart TD
    A["19.1 不相交集合操作"] --> B["基本概念"]
    A --> C["三个核心操作"]
    A --> D["应用:连通分量"]
    A --> E["分析框架"]

    B --> B1["不相交动态集合的划分"]
    B --> B2["代表 (representative)"]
    B --> B3["等价关系的维护"]

    C --> C1["MAKE-SET(x): 创建单元素集合"]
    C --> C2["FIND-SET(x): 返回所属集合的代表"]
    C --> C3["UNION(x, y): 合并两个集合"]

    D --> D1["CONNECTED-COMPONENTS"]
    D --> D2["SAME-COMPONENT"]
    D --> D3["遍历每条边执行UNION"]

    E --> E1["n = MAKE-SET 次数"]
    E --> E2["m = 总操作次数"]
    E --> E3["目标: 接近 O(m) 的总运行时间"]

    A --> F["后续优化路线"]
    F --> F1["19.2 链表表示: O(m + n lg n)"]
    F --> F2["19.3 有根树表示: O(m α(n))"]
    F --> F3["19.4 α(n) 的分析"]

核心思想

核心思路

不相交集合数据结构的核心思想是维护一组不相交的动态集合,并支持高效的合并查询操作。每个集合通过一个代表元素来标识,FIND-SET 返回代表,UNION 通过代表来合并集合。这种数据结构本质上是在维护一个等价关系上的划分——同一集合中的元素互相等价,不同集合中的元素互不等价。后续各节的关键优化思路是:用更聪明的数据结构和启发式策略,让 UNION 和 FIND-SET 尽可能快

不相交集合数据结构(Disjoint-Set Data Structure)

不相交集合数据结构维护一个动态集合的划分 ,其中每个 是一个动态集合,满足 )。每个集合通过一个代表(representative)来标识,同一集合中的每个元素都有相同的代表。该数据结构支持以下操作:

  • MAKE-SET():创建一个新的单元素集合
  • UNION(, ):将包含 的集合和包含 的集合合并为一个新集合(假设这两个集合当前不相交)
  • FIND-SET():返回一个指向包含 的集合的代表的指针

三个核心操作的详细说明

MAKE-SET()

  • 创建一个新的集合,其唯一成员(也是代表)为
  • 在无向图连通分量应用中,对每个顶点调用一次 MAKE-SET

FIND-SET()

  • 返回包含 的集合的代表
  • 代表的选择是任意的,但同一集合的所有元素必须返回相同的代表
  • 在连通分量应用中,FIND-SET 用于判断两个顶点是否属于同一连通分量

UNION(, )

  • 将包含 的集合和包含 的集合合并
  • 合并后需要选择一个新的代表(可以是原来任一集合的代表)
  • 前提条件: 当前在不同的集合中

应用:无向图的连通分量

不相交集合数据结构的一个经典应用是计算无向图的连通分量。给定一个无向图 ,目标是确定哪些顶点属于同一个连通分量。

CONNECTED-COMPONENTS 伪代码

算法执行流程

  1. 对图中的每个顶点 v 调用 MAKE-SET(v),将每个顶点初始化为独立的单元素集合
  2. 遍历每条边 (u, v)
  3. FIND-SET(u) ≠ FIND-SET(v)(即 u 和 v 不在同一集合中),则调用 UNION(u, v) 合并两个集合
  4. 处理完所有边后,每个集合就是一个连通分量
flowchart TD
    A["开始:输入图 G = (V, E)"] --> B["对每个顶点 v ∈ V<br/>调用 MAKE-SET(v)"]
    B --> C["取下一条边 (u, v) ∈ E"]
    C --> D{"FIND-SET(u)<br/>≠ FIND-SET(v)?"}
    D -- 是 --> E["UNION(u, v)<br/>合并两个集合"]
    E --> F{"还有未处理的边?"}
    D -- 否 --> F
    F -- 是 --> C
    F -- 否 --> G["结束:每个集合即为一个连通分量"]
CONNECTED-COMPONENTS(G)
1  for each vertex v ∈ G.V
2      MAKE-SET(v)
3  for each edge (u, v) ∈ G.E
4      if FIND-SET(u) ≠ FIND-SET(v)
5          UNION(u, v)

SAME-COMPONENT 伪代码

SAME-COMPONENT(u, v)
1  if FIND-SET(u) == FIND-SET(v)
2      return TRUE
3  else return FALSE

算法执行过程分析

  1. 第1-2行:对每个顶点创建一个单元素集合,共 次 MAKE-SET
  2. 第3-5行:遍历每条边,如果两个端点不在同一集合中则合并,共 次 FIND-SET 对(即 次 FIND-SET)和最多 次 UNION
  3. 算法结束后,同一连通分量的所有顶点在同一个集合中

直觉理解

可以把不相交集合想象成一群人组成的俱乐部。MAKE-SET 就是成立一个只有一个人的俱乐部;UNION 就是把两个俱乐部合并成一个;FIND-SET 就是查询某个人属于哪个俱乐部(通过俱乐部的”会长”来标识)。连通分量问题就是:一开始每个人都是独立的,每遇到一条”朋友关系”(边),就把这两个人所在的俱乐部合并,最终同一个俱乐部里的人互相之间都有直接或间接的朋友关系。

分析框架

分析参数

在分析不相交集合数据结构的效率时,使用以下两个参数:

  • :MAKE-SET 操作的次数(即元素总数)
  • :所有操作的总次数(包括 MAKE-SET、FIND-SET 和 UNION)

由于 (至少有 次 MAKE-SET),我们关心的是 次操作的总运行时间。理想目标是 ,即每次操作的摊还代价。后续各节将逐步逼近这个目标:

  • 19.2 节(链表表示):
  • 19.3 节(有根树表示 + 启发式):
  • 19.3 节(有根树 + 路径压缩):接近
  • 19.4 节(精确上界):,其中 是反阿克曼函数

补充理解与拓展

并查集的发明历史与关键论文

不相交集合数据结构(并查集)有着丰富的学术历史,以下是其发展中的关键里程碑:

  1. Hopcroft & Ullman (1973):在论文 “Set Merging Algorithms”(SIAM Journal on Computing, 2(4):294-303)中首次对并查集进行了系统分析,给出了 的运行时间上界。这里的 迭代对数(iterated logarithm),表示需要将 反复取对数多少次才能使结果降至常数以下。

  2. Tarjan (1975):在里程碑论文 “Efficiency of a Good But Not Linear Set Union Algorithm”(Journal of the ACM, 22(2):215-225)中,Tarjan 首次证明了使用路径压缩(path compression)的并查集算法的运行时间上界为 。其中 反阿克曼函数(inverse Ackermann function),对于所有实际可能的 值,。这个结果被认为是算法分析中最优美的渐近结果之一

  3. Tarjan & van Leeuwen (1984):在 “Worst-Case Analysis of Set Union Algorithms”(Journal of the ACM, 31(2):245-281)中统一了多种启发式策略的分析框架,证明了按秩合并(union by rank)与路径压缩的各种组合都能达到 的上界。

  4. Fredman & Saks (1989):在论文 “The Cell Probe Complexity of Dynamic Data Structures”(STOC ‘89)中证明了 是并查集问题的下界,这意味着 Tarjan 的 上界是渐近最优的——并查集不可能比这更快!

并查集与第16章摊还分析的关系

并查集的效率分析深度依赖摊还分析技术:

  • 19.2 节的链表表示使用聚合分析来证明加权合并启发式下每个操作的摊还代价
  • 19.4 节 上界证明使用势能方法,这是势能方法最经典、最精妙的应用之一

并查集的分析之所以需要摊还技术,是因为单个 FIND-SET 或 UNION 操作的最坏代价可能很高(如 甚至 ),但通过摊还分析可以证明操作序列的平均代价远低于最坏代价。这正是摊还分析的典型应用场景。


易混淆点与辨析

UNION 的前提条件:两个集合必须不相交

UNION(, ) 操作要求 当前在不同的集合中。如果 已经在同一集合中,调用 UNION 是未定义行为(或无操作)。在连通分量应用中,第4行的 if FIND-SET(u) ≠ FIND-SET(v) 检查正是为了确保这一前提条件。

代表的选择是任意的,但必须一致

每个集合的代表可以是该集合中的任意元素,但同一集合的所有元素通过 FIND-SET 必须返回相同的代表。代表的具体选择不影响算法的正确性,但会影响运行效率。后续节的优化(如按秩合并)正是通过巧妙选择代表来加速操作。

不相交集合 ≠ 普通集合

不相交集合数据结构中的”集合”是动态的——它们可以在运行过程中被创建、合并。这与数学中静态的集合概念不同。此外,不相交集合数据结构不支持从集合中删除元素、查询集合大小、遍历集合元素等操作(除非使用特定的表示方法,如19.2节的链表表示)。

的含义不要混淆

  • = MAKE-SET 操作次数 = 元素总数
  • = 所有操作的总次数(包括 MAKE-SET、FIND-SET 和 UNION)
  • 因此 ,且通常 远大于 (如连通分量问题中
  • 在分析中,我们关心的是 次操作的总时间,而非单个操作的时间

习题精选

题号题目描述难度
19.1-1CONNECTED-COMPONENTS 在给定图上的执行过程
19.1-2证明处理后两顶点在同一集合 iff 在同一连通分量⭐⭐
19.1-3FIND-SET 和 UNION 的调用次数分析⭐⭐

视频学习指南

资源主题链接说明
MIT 6.006 Lecture 12Disjoint Sets: Union-Findhttps://www.youtube.com/watch?v=0jNmHPfA_yEErik Demaine 讲解并查集基础概念与连通分量应用
Abdul BariUnion Find Algorithmhttps://www.youtube.com/watch?v=0jNmHPfA_yE直观的并查集入门讲解
WilliamFisetDisjoint Set / Union-Findhttps://www.youtube.com/watch?v=wU6udHRIkcc完整的并查集系列,含代码实现
NeetCodeUnion Findhttps://www.youtube.com/watch?v=8B1FALz3FHs算法竞赛视角的并查集讲解

教材原文(中文翻译)

CLRS 第4版 19.1节原文(中文翻译)

某些应用涉及将 个不同的元素划分为一组不相交的集合。这些应用常常需要对集合进行两种操作:寻找包含给定元素的唯一集合合并两个集合。本节研究一种维护不相交动态集合的数据结构。我们将看到,这些数据结构在确定无向图的连通分量时非常有用。

不相交集合操作

不相交集合数据结构维护一个不相交动态集合的集合 ,其中每个 是一个动态集合。我们用一个代表来标识每个集合,它是集合中的某个成员。在某些应用中,代表的选择并不重要,只要在查询时,对同一个集合中的两个元素返回相同的代表即可。在其他应用中,可能需要预先指定一个代表,例如选择集合中的最小元素。

不相交集合数据结构支持以下操作:

  • MAKE-SET():创建一个新的集合,其唯一成员(从而也是代表)为 。由于各个集合是不相交的,我们假设 当前不在任何其他集合中。
  • UNION(, ):将包含 的集合和包含 的集合合并为一个新集合。UNION 操作的代表性选择是任意的。虽然在很多情况下,UNION 的代表性选择无关紧要,但在某些应用中可能需要特定的选择。
  • FIND-SET():返回一个指向包含 的集合的代表的指针。

我们用以下两种方式来分析不相交集合数据结构的运行时间。我们考察 次 MAKE-SET 操作的序列,以及 个 MAKE-SET、UNION 和 FIND-SET 操作的混合序列,其中所有 个操作组成一个序列。我们分析 次操作的总运行时间。由于 MAKE-SET 操作构成一个序列,因此

连通分量的一个应用

不相交集合数据结构的一个典型应用是确定无向图中连通分量的个数。我们已经在第20章中看到过这个问题,但这里我们使用不相交集合来解决它。

给定一个无向图 ,CONNECTED-COMPONENTS 过程首先为每个顶点创建一个单元素集合。然后,对于每条边 ,它将包含 的集合合并。CONNECTED-COMPONENTS 过程可以用不相交集合操作实现如下:

对每个顶点 ,执行 MAKE-SET()。对每条边 ,如果 FIND-SET() FIND-SET(),则执行 UNION(, )。

为了判断两个顶点 是否在同一个连通分量中,我们只需检查 FIND-SET() 是否等于 FIND-SET()。


参见Wiki

第19章-用于不相交集合的数据结构 不相交集合操作