Ruixiang Li

Union-Find Set并查集

动态链接

并查集是通过将离散的元素连接起来union(p, q),并检查两个元素是否相连connected(p, q)的算法/数据结构。

我们可以直接用一个数组来表示这个结构,其下标就是元素编号,内容则是与其相连的节点。初始化就可以通过id[i] = i来进行。

从某种程度上,UF可以类比于一个无圈图(指图论中的无圈图),是有空的根或者一些没有明确根节点的树组成。

Quick Find

一个朴素的想法是将相连的元素对应的内容设置为同一个值,查找的时候只需要检查其内容是否相同即可;

可以类比键值对/树,相连的元素有相同的值/根节点

但是带来的问题则是union算法需要N级别的时间复杂度。

Quick Union

我们将这个数组改进为一个树的线性存储结构,即每个索引保存其父节点的索引,root for i is id[id[id...id[i]...]],这样查找只需要检查其根节点是否相同,而union算法只需要将一方的根节点的指向从自己变成另一方的根节点即可。

实际上是让这些树有了明确的根节点,connect用于判断两个元素是否在同一树上;union用于两棵树和合并。

但是这样的情况下,树的结构无法确定。在最坏情况下,不仅union算法的时间复杂度在$N$级别,connected算法也达到了$N$级别。(即查找根节点的问题。如果树非常细长、层数过深甚至无法保持树的结构,会急剧加大计算的开销)

优化

improve#1

第一种优化是Weighting带权。我们可以将一个树的大小(元素个数)作为一棵树的权,在每次合并树的时候,总是让较小的树成为较大树的子树,来避免树的高度过大。

这样,查找过程不变,union算法则需要额外维护一个size[]数组,以存储树的权。

可以证明,此时unionconnected的时间复杂度均取决于树的深度(根节点深度为0,高度为1),而树的深度一定不大于$\lg N$(以2为底的对数)

Pf.可以考虑什么时候树的高度会加大。显然,当两颗树大小相同时才会使合并后的树深度 + 1,而$N$个元素最多合并$\lg N$次。

或者也可以考虑这个树的结构。同样元素个数条件下,二叉树可以达到最大深度。

improve#2

第二种优化是路径压缩算法Path compression。其核心在于:每次查找结点p后,直接将p移动为其根节点的子树。这样可以极大的降低树的深度,而且只需要在查找根节点的代码中加入id[i]=id[id[i]]一行即可。

在对 $N$ 个元素的并查集进行 $M$ 次union-find 操作后访问数组最多$c(N+M\lg^N)$次,其中$\lg^$称为迭代对数函数,是指将一个数取对数变为 1 的次数,是一个增长非常缓慢的函数,$\lg^*(2^{65536})=5$,实际生活中可以认为其小于5。

实际上,在这种优化下,unionconnected函数的时间复杂度可以优化到阿克曼函数,是一个比上述函数还要慢的函数。我们可以认为这几乎是一种常数时间下的算法。

当然,Friedman和Sachs证明了并查集不存在真正的常数级算法,其时间复杂度为$O(\alpha(n))$的。

Result

WQUPC(Weighted Quick-Union with Path Conpression)带权路径压缩并查集是目前最优的并查集首先算法之一。实际应用中可以认为其时间复杂度为常数级。