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 ...
随机推荐
- Flask框架——Flask脚本、flask知识点补充
文章目录 Flask_脚本 1 集成Python shell 1.1 flask-script的用法: 1.1.1 实例:flask-script的简单实现 1.1.1命令添加方式: 第一种(无参命令 ...
- MySQL系列之读写分离架构——Atlas介绍、安装配置、Atlas功能测试、生产用户要求、Atlas基本管理、自动分表、关于读写分离建议
文章目录 1. Atlas介绍 2.安装配置 3. Atlas功能测试 4. 生产用户要求 5. Atlas基本管理 6. 自动分表 7. 关于读写分离建议 1. Atlas介绍 Atlas是由 Qi ...
- css中的字体样式
一.字体的样式 font-style:"normal" 正常 font-style:"italic"斜体 二.字体的粗细 font-weight:"b ...
- python---简单最大类间方差法(OTSU)算法
from matplotlib import pyplot as plt # cv2.imread()用于读取图片文件 # imread函数有两个参数,第一个参数是图片路径,第二个参数表示读取图片的形 ...
- React技术栈支援Vue项目,你需要提前了解的
写在前面 react整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,而vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听, 当属性变化的时候,响应 ...
- Android Studio里导入制作好的图片后,直接报错,图片名下方出现红色波浪线
症状:Android Studio里导入制作好的图片后,直接报错,图片名下方出现红色波浪线. 比如:bk_Image_BackGround,报红 解决办法:如:bk_Image_BackGround, ...
- Python 面向对象编程:类、对象、初始化和方法详解
Python 是一种面向对象的编程语言.在 Python 中,几乎所有东西都是对象,都具有其属性和方法. 类似于对象构造函数或用于创建对象的"蓝图"的类. 创建一个类 要创建一个类 ...
- 理解maven命令package、install、deploy的联系与区别(转)
https://blog.csdn.net/zhaojianting/article/details/80324533 我们在用maven构建java项目时,最常用的打包命令有mvn package. ...
- 基于iptables防火墙堵漏
之前在网上流传个段子:发现自己电脑被入侵,最有效的办法是即拔掉网线~ 虽然只是个段子却说明一旦机器发现漏洞被入侵,阻断入侵刻不容缓,无论对个人电脑和业务服务器都是如此. 商业服务器虽然有各种防护措施, ...
- 如何通过 wireshark 捕获 C# 上传的图片
一:背景 1. 讲故事 这些天计划好好研究下tcp/ip,以及socket套接字,毕竟工控中设计到各种交互协议,如果只是模模糊糊的了解,对分析此类dump还是非常不利的,而研究协议最好的入手点就是用抓 ...