「并查集」专题 6:并查集第 5 版 quick-union 基于路径压缩的非递归实现


「并查集」专题 6:并查集第 5 版 quick-union 基于路径压缩的非递归实现

重点提示:这一版代码用得最多。因为好理解,且代码量较少。

只用理解这一句即可 parent[p] = parent[parent[p]];,可以称之为「隔代压缩」。

虽然压缩不彻底,但是多压缩几次也就能够达到完全压缩的效果,且不使用递归,占用「递归栈」空间。

什么是路径压缩 path Compression?以上我们都是针对于 union 操作的优化,其实我们在 find 的时候,就可以对一棵树进行整理,让它的高度变低,这一点是基于并查集的一个特性:只要它们是连在一起的,其实谁指向谁,并不重要,所以我们在接下来的讨论中看到的并查集的表示图,它们是等价的,即它们表示的都是同一个并查集。

路径压缩 path Compression 的思路:在 find 的时候一次又一次的整理,将并查集构造(整理)成一个与之等价的并查集,使得后续的每一次 find 这样的操作路径最短。

假设一个最极端的并查集的图表示如下:

高级数据结构:并查集-9

我们开始找 $4$ 的父亲结点,$4$ 的父亲结点不是 $4$ ,说明不是根结点,此时,我们尝试 $2$ 步一跳,将 $4$ 的父亲结点改成 $4$ 的父亲结点的父亲结点,于是得到一个等价的并查集:

高级数据结构:并查集-10

下面我们该考察元素 $2$ 了,$2$ 的父亲结点是 $1$,$2$ 不是根结点,所以我们继续两步一跳,把 $2$ 的父亲结点设置成它的父亲结点的父亲结点,于是又得到一个等价的并查集:

高级数据结构:并查集-11

此时,整棵树的高度就在一次 find 的过程中被压缩了。这里有一个疑问:即使我们在最后只差一步的情况下,我们跳两步,也不会出现无效的索引。其实,一步一跳,两步一跳,甚至三步一跳都没有关系。

画图画了这么多,代码实现只有一行:parent[p] = parent[parent[p]]; 编写的时候要注意这行代码添加的位置,画一个示意图,想想这个路径压缩的过程,就不难写出。

Java 代码:

public int find(int p) {
    // 在 find 的时候执行路径压缩
    while (p != parent[p]) {
        // 编写这句代码的时候可能会觉得有点绕,
        // 画一个示意图,就能很直观地写出正确的逻辑
        // 两步一跳完成路径压缩
        parent[p] = parent[parent[p]];
        p = parent[p];
    }
    return p;
}

根据上面的图,我们通过 find 操作执行路径压缩,最终形成的并查集如下:

高级数据结构:并查集-12

文章作者: liweiwei1419
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 liweiwei1419 !
评论
 上一篇
「并查集」专题 7:quick-union 基于路径压缩的递归实现 「并查集」专题 7:quick-union 基于路径压缩的递归实现
「并查集」专题 7:并查集第 6 版 quick-union 基于路径压缩的递归实现 重点提示:这一节重要。 代码的实现的理解有一些绕。这一版我们实现压缩更彻底的路径压缩。其实我们不看上面的分析,我们想象路径压缩的目的是什么,就是让我们的
下一篇 
「并查集」专题 5:并查集第 4 版 quick-union 基于 rank 的优化 「并查集」专题 5:并查集第 4 版 quick-union 基于 rank 的优化
「并查集」专题 5:并查集第 4 版 quick-union 基于 rank 的优化 重点提示:基于 rank 的优化是使用得更多的,因为这样做更合理一些。 但是维护数组 rank 的定义是相对麻烦的,通常的做法就是不维护,只是把数组 ra
  目录