Lec8 Disjoint Set Class
Lec8 Disjoint Set Class
欢迎来到并查集,被称为最简洁好用的数据结构。😎
等价关系(Equivalence Relations)
等价关系的定义
- 关系 \(R\) 定义在集合 \(S\) 上,意味着对于集合 \(S\) 中的任意一对元素 \(a, b \in S\),\(a \, R \, b\) 要么为真,要么为假。
等价关系的三大性质
一个关系 \(R\) 是等价关系,当且仅当它满足以下三条性质:
- 自反性(Reflexive):对于所有的 \(a \in S\),都有 \(a \, R \, a\)。
- 对称性(Symmetric):如果 \(a \, R \, b\),那么 \(b \, R \, a\),对于所有 \(a, b \in S\)。
- 传递性(Transitive):如果 \(a \, R \, b\) 且 \(b \, R \, c\),则有 \(a \, R \, c\),对于所有 \(a, b, c \in S\)。
等价关系的示例
- “≤” 和 “≥” 并不是等价关系,因为它们不满足对称性。
- “=(等于)” 是等价关系,因为它满足自反性、对称性和传递性。
- “属于同一类” 和 “电连接性” 是等价关系,因为它们满足上述三条性质。
等价类(Equivalence Class)
等价类:给定一个元素 \(a \in S\),它的等价类是集合 \(S\) 中所有与 \(a\) 有关系的元素的子集。
等价类的性质:
\(S\) 中不同的等价类是不相交的。
每个元素 \(a \in S\) 都恰好属于一个等价类。
等价问题(Equivalence Problem)
- 问题描述:给定一个等价关系 \(R\),决定集合 \(S\) 中一对元素 \(a, b \in S\) 是否满足 \(a \, R \, b\),即它们是否属于同一个等价类。
- 解决方案:可以通过检查 \(a\) 和 \(b\) 是否在同一个等价类来判断它们是否有关联。
动态等价问题(Dynamic Equivalence Problem)
问题描述
- 策略:
- 从每个元素的单元素集合开始,这些单元素集合是不相交的。
- 进行两种操作:
- Find:查找给定元素的等价类(集合)。
- Union:合并两个集合。
- 这是一个动态(在线)问题,因为集合会在操作过程中发生变化,而 Find 操作必须能够应对这些变化。
不相交集的并查集(Disjoint Union/Find)
- 目标:实现对集合的查找(Find)和合并(Union)操作。
两种策略实现 Find 和 Union
优化 Find 操作:确保 Find 操作在最坏情况下的时间复杂度为常数时间(即 O(1))。这通常通过路径压缩(Path Compression)技术实现。路径压缩可以减少树的深度,从而加速后续的查找操作。
优化 Union 操作:确保 Union 操作在最坏情况下的时间复杂度为常数时间(即 O(1))。这通常通过按秩合并(Union by Rank 或 Union by Size)来实现,按秩合并总是将较小的树合并到较大的树下,从而避免树的深度过大。
约束
- 不可能同时优化 Find 和 Union 操作的时间复杂度:在实际应用中,无法同时保证 Find 和 Union 操作都在常数时间内完成。通常是通过优化其中一个操作,而另一个操作的时间复杂度会有所妥协。
Up-Tree for D-U/F(并查集的树结构表示)
1. Up-Tree 结构介绍
- 并查集(Disjoint Set Union/Find,简称 D-U/F)利用树形结构表示每个集合,每个元素通过父链接(parent link)指向其父节点。
- 如果元素是根节点,则该元素的父节点值为 -1。
2. 树结构示例
- 初始状态(森林):每个元素都是一个独立的集合,根节点指向自己(父链接为 -1)。
- 中间状态:通过合并操作(union),集合之间会形成树状结构,根节点指向其他元素,元素之间通过父链接连接。
3. 操作介绍
- Find(x):从元素 x 开始,沿着父链接一直查找,直到找到根节点。返回根节点即为该元素所在的集合。(Find(x) follow x to the root and return the root )
- Union(i, j):假设元素 i 和 j 是两个集合的根节点,通过将 j 的根节点指向 i 来合并这两个集合。(Union(i,j) - assuming i and j roots, point j to i.)
4. 代码实现
1 | class DisjSets { |
5. Up-Tree 的特点
- Union 操作:每次合并两个集合时,都是将其中一个集合的根节点指向另一个集合的根节点,从而使得树的高度增加。
- Find 操作:查找操作从一个元素开始,通过递归的方式逐层向上查找父节点,直到找到根节点。
6. 复杂度分析
- Union 操作:在最坏情况下,Union 操作的时间复杂度为 O(1),因为每次合并只是简单地将一个根指向另一个根。
- Find 操作:查找操作的时间复杂度可能较高,但通过优化(如路径压缩),可以将其优化为接近常数时间。
坏情况与加权并查集优化
1. 坏情况示例
- Union(2, 1)
- Union(3, 2)
- Union(n, n-1)
- Find(1) —— 需要
n
步。
这表明在最坏情况下,如果每次都进行简单的合并操作(不做优化),查找一个元素的操作可能需要遍历整棵树,从而导致时间复杂度为
O(n),即 n
步操作。
2. 加权并查集(Weighted Union)
- 加权并查集:为了避免树的高度过大,我们可以采用按树的大小(节点数)加权来合并集合。即每次合并时,总是将较小的树合并到较大的树下面,从而保持树的平衡。
- Union-by-Size(按大小合并):始终将较小的树连接到较大的树上,这样能有效减少树的高度。
3. 加权并查集的性质
- 加权并查集的树高度(h)与树的权重(n)之间的关系:
- 一个加权并查集的上树(Up-Tree)高度为
h
时,它的权重至少为 \(2^h\)。 - 证明:通过归纳法证明该关系。
- 基础情况:当高度
h = 0
时,上树只有一个节点,权重为 \(2^0 = 1\),符合条件。 - 归纳步骤:假设对于所有高度小于
h
的上树,树的权重都至少为 \(2^{h-1}\),现在考虑一个高度为h
的上树。最小的加权并查集树在高度为h
时,至少需要合并两个权重为 \(2^{h-1}\) 的子树,因此其权重至少为 \(2^h\)。
- 基础情况:当高度
- 一个加权并查集的上树(Up-Tree)高度为
4. 时间复杂度分析
- 对于加权并查集,通过按大小合并,可以保证树的高度不会过高。具体来说,假设树的权重为
n
,则其高度h
满足:- \(n \geq 2^h\)
- 通过对数转换:\(\log_2 n \geq h\)
- Find(x) 操作的时间复杂度:在树 T 中查找元素
x
需要经过的步数与树的高度成正比,因此其时间复杂度为 O(log n)。
关于空间上的问题
加权并查集最坏情况分析与优化
1. 最坏情况:N/2 次加权并查集操作
加权并查集(Weighted Union)的最坏情况可能发生在树的高度增长过快的情况下。假设进行的加权并查集操作数量为 \(N - 1\),其中每次合并的操作依次将树的高度增加一倍。具体来说,可以有如下的加权操作:
- 第一次操作:\(N/2\) 次加权并查集操作。
- 第二次操作:\(N/4\) 次加权并查集操作。
- …依此类推,直到 \(1\) 次加权并查集操作。
如果树的深度过高,可能导致查找操作需要沿着树的路径进行多次,增加时间复杂度。
2. 树的高度与最坏情况
- 假设有 \(N = 2^k\) 个节点,经过多次加权并查集操作后,树的高度将达到 \(k\),即最长路径从叶节点到根节点的长度为 \(k\)。
- 这意味着,查找操作(Find)可能需要 \(O(k) = O(\log N)\) 的时间。
3. 替代实现:按高度合并(Union-by-Height)
- 为了避免树的高度增长过快,我们可以使用按高度合并(Union-by-Height)策略。这个策略与按大小合并(Union-by-Size)不同,按高度合并关注的是合并树时,始终将高度较小的树合并到高度较大的树中,从而确保树的高度增长较慢。
Union-by-Height 代码示例
1 | /** |
4. 按高度合并的效果
- 在按高度合并的策略下,树的深度始终不会超过 $ _2 N $,从而避免了树的高度过大,确保了查找操作的时间复杂度始终维持在 $ O(N) $。
- 树的深度:每次合并时,较低的树将被附加到较高的树上,这保证了树的高度始终保持在 $ O(N) $ 级别,从而避免了最坏情况的发生。
5. 总结
- 使用加权并查集(无论是按大小合并还是按高度合并)都能有效减少树的高度,从而提高查找操作的效率。
- 按高度合并(Union-by-Height)策略比简单的加权并查集(按树的大小合并)在实际应用中更常用,因为它保证了树的高度始终保持较小,进一步优化了查找操作的时间复杂度。
- 最坏情况发生时,即使经过多次合并操作,树的高度也只会是 $ O(N) $,从而避免了树的深度过大导致查找效率低下的问题。
路径压缩(Path Compression)与加权并查集(Weighted Union)结合的优化
1. 路径压缩 (Path Compression) 的原理
- 目标:在执行 Find 操作时,将查找路径上的所有节点直接指向树的根节点,从而扁平化树的结构,减少后续查找操作的路径长度。
- 实现:通过递归实现路径压缩,在返回根节点的同时更新路径上的每个节点,使它们直接指向根节点。
路径压缩的代码实现
1 | /** |
- 如果节点 \(x\) 不是根节点,递归调用
find(s[x])
查找根节点,并将路径上的所有节点直接连接到根节点。
2. 加权并查集与路径压缩结合的优化
- 加权并查集 (Weighted Union):
- 将节点较少的树合并到节点较多的树上,减少树的高度。
- 保证合并操作的最坏时间复杂度为 \(O(1)\)。
- 路径压缩 (Path Compression):
- 在每次查找操作中扁平化树的结构,降低后续操作的路径长度。
- 查找操作的最坏时间复杂度为 \(O(\log N)\)。
- 结合后的特点:
- 单次操作的时间复杂度可能较高,但 m 次操作的总时间复杂度为 \(O(m \cdot \log^* N)\)。
- \(\log^* N\) 是一个增长极其缓慢的函数,在实际应用中,几乎是常量时间。
3. \(\log^* N\) 的定义与意义
- \(\log^* N\)
是迭代对数函数,表示在对数操作下,达到 1 所需的最少迭代次数:
- \(\log^* 2 = 1\)
- \(\log^* 4 = 2\) (因为 \(\log_2(4) = 2\))
- \(\log^* 16 = 3\) (因为 \(\log_2(16) = 4 \rightarrow \log_2(4) = 2 \rightarrow \log_2(2) = 1\))
- \(\log^* 65536 = 4\) (因为 \(65536 = 2^{16}\))
- \(\log^* 2^{65536} = 5\)
- 意义:即使对于非常大的 \(N\),\(\log^* N\) 依然是一个很小的值,通常小于 5。
4. 性能分析
- 最坏情况分析:
- Find 操作:\(O(\log N)\),因为路径压缩可能需要多次递归直到找到根节点。
- Union 操作:\(O(1)\),通过加权合并树可以在常数时间完成。
- 整体时间复杂度:
- \(m \geq N\) 次操作的总时间复杂度为 \(O(m \cdot \log^* N)\),这对于实际应用来说可以近似看作常数时间。
5. 路径压缩与加权并查集的优点
- 高效性:操作的平均时间复杂度接近常数。
- 可扩展性:适用于处理动态集合问题,例如图的连通性问题。
- 实际应用:即使面对大规模数据,结合路径压缩的加权并查集也能保持高效。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Totoroの旅!
评论