UnionFind 并查集
简介
UnionFind 主要用于解决图论中的动态联通性的问题(对于输入的一系列元素集合,判断其中的元素是否是相连通的)。
以下图为例:
集合[1, 2, 3, 4] 和 [5, 6]中的每个元素之间都是相联通的,及 1 和 2、3、4都是连通的,而 1 和 5 则不是连通的。
UnionFind 主要的 API:
void union(int p, int q); // 将 p 和 q 连接起来
boolean connect(int p, int q); // 判断 p 和 q 是否是相互连通的
这里的 “连通” 有以下性质:
- 自反性:p 和 p 自身是连通的
- 对称性:如果 p 和 q 是连通的,那么 q 和 p 也是连通的
- 传递性:如果 p 和 q 是连通,并且 q 和 r 是连通的,那么 p 和 r 也是联通的
实现
使用数组来存储连接的节点是一个可选的方案,使用数组 parent 来表示每个索引元素的直接连接节点,parent 的每个元素代表当前索引位置对应的元素的直接连接元素。
以上文的示例为例,初始时每个元素的连接情况如下图所示:

因为每个元素此时是没有与其他元素连接的,此时连接的元素就是它本身
此时的 parent 元素组成如下所示:

将 2 连接到 1、4 连接到 3、6 连接到5,连接起来,此时的连接情况如下所示:

此时 parent 中的情况如下所示:

再将 3-4 连接到 1-2,此时的连接情况如下所示:

此时的 parent 中各个元素的对应的元素值如下所示:

这样就实现了刚开始举出的情况,现在,通过 parent就可以检测两个元素是否是“连”通的了。
具体的实现:
public class UnionFind {
private final int[] parent; // 记录每个元素的连接信息
private int count; // 用于记录当前的集合数量
UnionFind(final int n) {
parent = new int[n];
count = n;
// 初始化每个节点的父节点,使得每个节点连接到自身
for (int i = 0; i < n; ++i) {
parent[i] = i;
}
}
/*
用于判断两个元素之间是否是连通的,
只要判断两个元素对应的根节点是否是一致的就可以判断两个节点是否是连通的
*/
public boolean connect(int p, int q) {
return find(p) == find(q);
}
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
// 如果两个节点存在共同的根节点,那么它们就是连通的,不需要进行进一步的操作
if (rootP == rootQ) return;
parent[rootP] = rootQ; // 将 p 的根节点连接到 q 的根节点,这样 p 和 q 就是连通的
count--;
}
public int count() {return count;}
// 找到当前元素 p 的连接的根节点
private int find(int p) {
while (p != parent[p]) // 由于根节点不会存在连接到其它节点的情况,因此它连接的节点就是它本身
p = parent[p];
return p;
}
}
实现优化
在上文提到的实现中,每次将两个不相连的节点进行连接操作,在最坏的情况下会导致这个集合成为一条链,从而每次的查找操作都需要在 \(O(n)\) 的时间复杂度内完成。以上文的初始集合为例,考虑以下的连接顺序:1->2、1->3、1->4、1->5、1->6,最后得到的结果如下图所示:

考虑这么一种情况,对于两个集合来讲,是将大集合连接到小集合得到的集合高度会小一些,还是将小集合连接到大集合得到的集合高度会小一些?以上文的例子为例,将 1-4 的集合连接到 5-6 的集合,得到的连接情况如下所示:

而如果将 5-6 的集合连接到 1- 4 的集合中,得到的连接情况如下所示:

很明显,将 5-6 的集合插入到 1-4 中的高度更小,这是因为较大的集合有更多的位置容纳新连接的节点。因此,在连接时人为地将小集合连接到大集合可以有效地降低 UnionFind 每个操作的时间复杂度:
public class UnionFind {
private final int[] parent;
private final int[] sz; // 用于统计当前的元素包含的元素个数
private int count;
UnionFind(final int n) {
parent = new int[n];
sz = new int[n];
count = n;
for (int i = 0; i < n; ++i) {
parent[i] = i;
sz[i] = 1; // 每个节点在初始状态的时候集合的元素个数都是 1
}
}
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
// 将小集合连接到大集合,同时更新对应的集合元素个数
if (sz[rootP] > sz[rootQ]) {
parent[rootQ] = rootP;
sz[rootP] += sz[rootQ];
} else {
parent[rootP] = rootQ;
sz[rootQ] += sz[rootP];
}
count--;
}
// 省略部分代码
}
经过这么一顿操作之后,现在每个节点到根节点的高度为 \(logN\) ,相比较之前在最坏的情况下会生成一条链而形成 \(N\) 的高度,这种情况就要好很多了。
进一步的优化
实际上,上文给出的优化已经很好了,但是依旧存在可以优化的地方:将每个节点直接连接到根节点而不是父节点,可以进一步降低查找操作的时间复杂度。这中优化的方式也被称为 “路径压缩”,因为省去了向上查找根节点的操作。
// 其余代码同上
private int find(int p) {
while (p != parent[p]) {
// 更新当前的元素的父节点为父节点的父节点,因为根节点的父节点为它本身,因此最终会向上压缩一节高度
parent[p] = parent[parent[p]];
p = parent[p];
}
return p;
}
经过进一步的优化之后,理论上 UnionFind 的每个操作都为 \(O(1)\) 的时间复杂度,但是实际上使用“路径压缩”的方式进行优化并不会带来很大的性能提升。
实际使用
比较经典的一个实际使用是有关渗透阈值的估计,具体内容可以查看 https://coursera.cs.princeton.edu/algs4/assignments/percolation/specification.php
UnionFind 并查集的更多相关文章
- UVA 11987 - Almost Union-Find(并查集)
UVA 11987 - Almost Union-Find 题目链接 题意:给定一些集合,操作1是合并集合,操作2是把集合中一个元素移动到还有一个集合,操作3输出集合的个数和总和 思路:并查集,关键在 ...
- Almost Union-Find 并查集(脱离原来的树)
h: 0px; "> I hope you know the beautiful Union-Find structure. In this problem, you’re to im ...
- Union-find 并查集
解决问题 给一系列对点0~N-1的连接,判断某两个点p与q是否相连. private int[] id; // 判断p和q是否属于同一个连通分量 public boolean connected(in ...
- UVA - 11987 Almost Union-Find 并查集的删除
Almost Union-Find I hope you know the beautiful Union-Find structure. In this problem, you're to imp ...
- UVA 11987 Almost Union-Find 并查集单点修改
Almost Union-Find I hope you know the beautiful Union-Find structur ...
- Union-Find 并查集算法
一.动态连通性(Dynamic Connectivity) Union-Find 算法(中文称并查集算法)是解决动态连通性(Dynamic Conectivity)问题的一种算法.动态连通性是计算机图 ...
- UVA 11987 - Almost Union-Find 并查集的活用 id化查找
受教了,感谢玉斌大神的博客. 这道题最难的地方就是操作2,将一个集合中的一个点单独移到另一个集合,因为并查集的性质,如果该点本身作为root节点的话,怎么保证其他点不受影响. 玉斌大神的思路很厉害,受 ...
- UVA11987 Almost Union-Find 并查集的节点删除
题意: 第一行给出一个n,m,表示 n个集合,里面的元素为1~n,下面有m种操作,第一个数为 1 时,输入a,b 表示a,b 两个集合合并到一起,第一个数为 2 时,输入a,b表示将 a 从他原来的集 ...
- UVA - 11987 Almost Union-Find[并查集 删除]
UVA - 11987 Almost Union-Find I hope you know the beautiful Union-Find structure. In this problem, y ...
- 132.1.001 Union-Find | 并查集
@(132 - ACM | 算法) Algorithm | Coursera - by Robert Sedgewick > Tip: Focus on WHAT is really impor ...
随机推荐
- 2023-09-27:用go语言,在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始, 并尝试进行 k 次移动。行和列是 从 0 开始 的,所以左上单元格是 (0
2023-09-27:用go语言,在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始, 并尝试进行 k 次移动.行和列是 从 0 开始 的,所以左上单元格是 (0 ...
- JUC并发编程(2)—synchronized锁原理
目录 乐观锁和悲观锁介绍 synchronized用法介绍 synchronized和ReentrantLock的区别 经典8锁问题案例 从字节码角度分析synchronized实现 synchron ...
- Springboot集成Netty实现TCP通讯
Netty测试客户端 package com.coremain; import com.coremain.handler.ServerListenerHandler; import io.netty. ...
- 2020 ICPC 南京站
gym A. Ah, It's Yesterday Once More 有趣的题,但场上的人恐怕不会这么想( 构造一条长路径,且拐弯处在不同边界.这样每条竖线合并后都在一边,还需要走一遍才能合并到一起 ...
- MySQL系列之主从复制基础——企业高可用性标准、主从复制简介、主从复制前提(搭建主从的过程)、主从复制搭建、主从复制的原理、主从故障监控\分析\处理、主从延时监控及原因
文章目录 0.企业高可用性标准 *** 0.1 全年无故障率(非计划内故障停机) 0.2 高可用架构方案 1. 主从复制简介 ** 2. 主从复制前提(搭建主从的过程) *** 3. 主从复制搭建(C ...
- 解决在VS Code中运行有中文字符的Java代码(第三种方式),出现编码 GBK 的不可映射字符 (0x81)
写代码时,我们不避免的会使用一些中文注释,这些在其他的语言中没有问题.但是在Java的注释里面如果有中文字符,就会报错.即使文件编码是utf-8也无济于事.是因为使用CMD运行java程序的时候,系统 ...
- Docker 日志自动轮转和清理配置
设置 Docker 日志大小和自动删除旧日志:通过配置 Docker 使用 json-file 日志驱动,同时使用 logrotate 工具,可以设置日志的最大大小(例如100MB),并在达到该大小时 ...
- NW排错
fist date VM备份失败时: NW server上(linux): > nsradmin >p type : nsr recover > cd /nsr/logs >n ...
- Docker磁盘&内存&CPU资源实战
Docker 资源实战:cpu/内存配置: #查看帮助 docker run --help docker update --help #配置容器使用cpu /内存大小--privileged 给与容器 ...
- 最新 2023.2 版本 IDEA 永久破解教程,IDEA 破解补丁永久激活(亲测有效)
最近 jetbrains 官方发布了 2023.2 版本的 IDEA,之前的激活方法并不支持这个新的版本. 下面是最新的激活教程,激活步骤和之前是类似的,只是换用了不同的补丁文件. 本教程支持 Jet ...