Unverified Commit f780e68f authored by Xeonacid's avatar Xeonacid Committed by GitHub
Browse files

Merge pull request #2516 from zyj2297349886/dsu

修缮了Dsu的部分格式
parents 4afcb528 d10dd7af
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -84,11 +84,11 @@ void unionSet(int x, int y) {

由于需要我们支持的只有集合的合并、查询操作,当我们需要将两个集合合二为一时,无论将哪一个集合连接到另一个集合的下面,都能得到正确的结果。但不同的连接方法存在时间复杂度的差异。具体来说,如果我们将一棵点数与深度都较小的集合树连接到一棵更大的集合树下,显然相比于另一种连接方案,接下来执行查找操作的用时更小(也会带来更优的最坏时间复杂度)。

当然,我们不总能遇到恰好如上所述的集合————点数与深度都更小。鉴于点数与深度这两个特征都很容易维护,我们常常从中择一,作为估价函数。而无论选择哪一个,时间复杂度都为 $\Theta (m\alpha(m,n))$ ,具体的证明可参见 References 中引用的论文。
当然,我们不总能遇到恰好如上所述的集合————点数与深度都更小。鉴于点数与深度这两个特征都很容易维护,我们常常从中择一,作为估价函数。而无论选择哪一个,时间复杂度都为 $O (m\alpha(m,n))$ ,具体的证明可参见 References 中引用的论文。

在算法竞赛的实际代码中,即便不使用启发式合并,代码也往往能够在规定时间内完成任务。在 Tarjan 的论文[1]中,证明了不使用启发式合并、只使用路径压缩的最坏时间复杂度是 $\Theta (m \log n)$ 。在姚期智的论文[2]中,证明了不使用启发式合并、只使用路径压缩,在平均情况下,时间复杂度依然是 $\Theta (m\alpha(m,n))$ 。
在算法竞赛的实际代码中,即便不使用启发式合并,代码也往往能够在规定时间内完成任务。在 Tarjan 的论文[1]中,证明了不使用启发式合并、只使用路径压缩的最坏时间复杂度是 $O (m \log n)$ 。在姚期智的论文[2]中,证明了不使用启发式合并、只使用路径压缩,在平均情况下,时间复杂度依然是 $O (m\alpha(m,n))$ 。

如果只使用启发式合并,而不使用路径压缩,时间复杂度为 $\Theta(m\log n)$ 。由于路径压缩单次合并可能造成大量修改,有时路径压缩并不适合使用。例如,在可持久化并查集、线段树分治 + 并查集中,一般使用只启发式合并的并查集。
如果只使用启发式合并,而不使用路径压缩,时间复杂度为 $O(m\log n)$ 。由于路径压缩单次合并可能造成大量修改,有时路径压缩并不适合使用。例如,在可持久化并查集、线段树分治 + 并查集中,一般使用只启发式合并的并查集。

此处给出一种 C++ 的参考实现,其选择点数作为估价函数:

@@ -110,7 +110,7 @@ void unionSet(int x, int y) {

同时使用路径压缩和启发式合并之后,并查集的每个操作平均时间仅为 $O(\alpha(n))$ ,其中 $\alpha$ 为阿克曼函数的反函数,其增长极其缓慢,也就是说其单次操作的平均运行时间可以认为是一个很小的常数。

 [Ackermann 函数](https://en.wikipedia.org/wiki/Ackermann_function)  $A(n, m)$ 的定义是这样的:
 [Ackermann 函数](https://en.wikipedia.org/wiki/Ackermann_function)  $A(m, n)$ 的定义是这样的:

 $A(m, n) = \begin{cases}n+1&\text{if }m=0\\A(m-1,1)&\text{if }m>0\text{ and }n=0\\A(m-1,A(m,n-1))&\text{otherwise}\end{cases}$