第三十一篇 玩转数据结构——并查集(Union Find)
- 查看"网络"中节点的连接状态,这里的网络是广义上的网络
- 数学中的集合类的实现
- 对于一组数据,并查集主要支持两种操作:合并两个数据、判断两个数据是否属于同一集合(两个数据是否连接)
- 并查集的接口业务逻辑如下:
public interface UF { int getSize(); boolean isConnected(int p, int q); void unionElements(int p, int q); }
- 第一版并查集Quick Find,业务逻辑如下:
public class UnionFind1 implements UF { private int[] id; public UnionFind1(int size) { id = new int[size];
for (int i = 0; i < id.length; i++) {
id[i] = i;
}
} // 实现getSize方法
@Override
public int getSize() {
return id.length;
} private int find(int p) { if (p < 0 || p >= id.length) {
throw new IllegalArgumentException("p is out of bound.");
}
return id[p];
} // 实现isConnected方法,查看元素p与元素q是否所属同一个集合
@Override
public boolean isConnected(int p, int q) {
return id[p] == id[q];
} // 实现unionElements方法,合并元素p和元素q所属集合
@Override
public void unionElements(int p, int q){ int pID = find(p);
int qID = find(q); if(pID == qID){
return;
}
for(int i=0;i<id.length;i++){
if(id[i]==pID){
id[i] = qID;
}
}
}
}- Quick FInd的时间复杂度分析

- 第二版并查集Quick Union,业务逻辑如下:
public class UnionFind2 implements UF { private int[] parent; public UnionFind2(int size) { parent = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
}
} @Override
public int getSize() {
return parent.length;
} private int find(int p) { if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
p = parent[p];
}
return p;
} // 实现isConnected方法,判断元素p与元素q是否同属一个集合
@Override
public boolean isConnected(int p, int q) {
return parent[p] == parent[q];
} // 实现unionElements方法,合并元素p和元素q所在集合
@Override
public void unionElements(int p, int q) { int pRoot = find(p);
int qRoot = find(q); if (pRoot == qRoot) {
return;
}
parent[pRoot] = qRoot;
}
}- Quick Union的时间复杂度分析
isConnected O(h) 其中,h为树的高度
unionElements O(h)- 测试Quick Find和Quick Union的性能
- 测试的业务逻辑如下:
import java.util.Random; public class Main { private static double testUF(UF uf, int m) { int size = uf.getSize();
Random random = new Random();
long startTime = System.nanoTime(); for (int i = 0; i < m; i++) {
int a = random.nextInt(size);
int b = random.nextInt(size);
uf.unionElements(a, b);
} for (int i = 0; i < m; i++) {
int a = random.nextInt(size);
int b = random.nextInt(size);
uf.isConnected(a, b);
} long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
} public static void main(String[] args) {
int size = 100000;
int m = 10000; UnionFind1 uf1 = new UnionFind1(size);
double time1 = testUF(uf1, m);
System.out.println("Quick Find, time: " + time1 + " s"); UnionFind2 uf2 = new UnionFind2(size);
double time2 = testUF(uf2, m);
System.out.println("Quick Union, time: " + time2 + " s");
}
}- 输出结果:
Quick Find, time: 0.272248873 s
Quick Union, time: 0.001273318 s
- 对unionElements方法进行优化,使元素少的节点指向元素多的节点
- 优化后的业务逻辑如下:
public class UnionFind3 implements UF { private int[] parent;
private int[] sz; public UnionFind3(int size) { parent = new int[size];
sz = new int[size]; for (int i = 0; i < size; i++) {
parent[i] = i;
sz[i] = 1;
}
} @Override
public int getSize() {
return parent.length;
} private int find(int p) { if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
p = parent[p];
}
return p;
} // 实现isConnected方法,判断元素p与元素q是否同属一个集合
@Override
public boolean isConnected(int p, int q) {
return parent[p] == parent[q];
} // 实现unionElements方法,合并元素p和元素q所在集合
@Override
public void unionElements(int p, int q) { int pRoot = find(p);
int qRoot = find(q); if (pRoot == qRoot) {
return;
}
if (sz[pRoot] < sz[qRoot]) {
parent[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
}else{
parent[qRoot] = pRoot;
sz[pRoot]+=sz[qRoot];
}
}
}
- 对unionElements方法进行优化,使深度浅节点指向深度更深的节点
- 优化后的业务逻辑如下:
public class UnionFind4 implements UF { private int[] parent;
private int[] rank; public UnionFind4(int size) { parent = new int[size];
rank = new int[size]; for (int i = 0; i < size; i++) {
parent[i] = i;
rank[i] = 1;
}
} @Override
public int getSize() {
return parent.length;
} private int find(int p) { if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
p = parent[p];
}
return p;
} // 实现isConnected方法,判断元素p与元素q是否同属一个集合
@Override
public boolean isConnected(int p, int q) {
return parent[p] == parent[q];
} // 实现unionElements方法,合并元素p和元素q所在集合
@Override
public void unionElements(int p, int q) { int pRoot = find(p);
int qRoot = find(q); if (pRoot == qRoot) {
return;
}
if (rank[pRoot] < rank[qRoot]) {
parent[pRoot] = qRoot;
} else if (rank[qRoot] < rank[pRoot]) {
parent[qRoot] = pRoot;
} else {
parent[pRoot] = qRoot;
rank[qRoot] += 1;
}
}
}
- 对find方法进行优化,实现简单路径压缩(非递归实现)
- 优化后业务逻辑如下
public class UnionFind5 implements UF { private int[] parent;
private int[] rank; public UnionFind5(int size) { parent = new int[size];
rank = new int[size]; for (int i = 0; i < size; i++) {
parent[i] = i;
rank[i] = 1;
}
} @Override
public int getSize() {
return parent.length;
} private int find(int p) { if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
parent[p] = parent[parent[p]]; // 优化了这里
p = parent[p];
}
return p;
} // 实现isConnected方法,判断元素p与元素q是否同属一个集合
@Override
public boolean isConnected(int p, int q) {
return parent[p] == parent[q];
} // 实现unionElements方法,合并元素p和元素q所在集合
@Override
public void unionElements(int p, int q) { int pRoot = find(p);
int qRoot = find(q); if (pRoot == qRoot) {
return;
}
if (rank[pRoot] < rank[qRoot]) {
parent[pRoot] = qRoot;
} else if (rank[qRoot] < rank[pRoot]) {
parent[qRoot] = pRoot;
} else {
parent[pRoot] = qRoot;
rank[qRoot] += 1;
}
}
}- 再次优化find方法,实现终极路径压缩(递归实现)
public class UnionFind6 implements UF { private int[] parent;
private int[] rank; public UnionFind6(int size) { parent = new int[size];
rank = new int[size]; for (int i = 0; i < size; i++) {
parent[i] = i;
rank[i] = 1;
}
} @Override
public int getSize() {
return parent.length;
} private int find(int p) { if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
if (p != parent[p]) {
parent[p] = find(parent[p]); // 优化了这里
}
return parent[p];
} // 实现isConnected方法,判断元素p与元素q是否同属一个集合
@Override
public boolean isConnected(int p, int q) {
return parent[p] == parent[q];
} // 实现unionElements方法,合并元素p和元素q所在集合
@Override
public void unionElements(int p, int q) { int pRoot = find(p);
int qRoot = find(q); if (pRoot == qRoot) {
return;
}
if (rank[pRoot] < rank[qRoot]) {
parent[pRoot] = qRoot;
} else if (rank[qRoot] < rank[pRoot]) {
parent[qRoot] = pRoot;
} else {
parent[pRoot] = qRoot;
rank[qRoot] += 1;
}
}
}
第三十一篇 玩转数据结构——并查集(Union Find)的更多相关文章
- 第三十三篇 玩转数据结构——红黑树(Read Black Tree)
1.. 图解2-3树维持绝对平衡的原理: 2.. 红黑树与2-3树是等价的 3.. 红黑树的特点 简要概括如下: 所有节点非黑即红:根节点为黑:NULL节点为黑:红节点孩子为黑:黑平衡 4.. 实现红 ...
- 第三十篇 玩转数据结构——字典树(Trie)
1.. Trie通常被称为"字典树"或"前缀树" Trie的形象化描述如下图: Trie的优势和适用场景 2.. 实现Trie 实现Trie的业务无 ...
- 算法手记 之 数据结构(并查集详解)(POJ1703)
<ACM/ICPC算法训练教程>读书笔记-这一次补上并查集的部分.将对并查集的思想进行详细阐述,并附上本人AC掉POJ1703的Code. 在一些有N个元素的集合应用问题中,通常会将每个元 ...
- ACM数据结构-并查集
ACM数据结构-并查集 并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合 ...
- 【题解】P2024 [NOI2001]食物链 - 数据结构 - 并查集
P2024 [NOI2001]食物链 声明:本博客所有题解都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。 题目描述 动物王国中有三类动物 \(A,B ...
- 图论&数据结构——并查集
Wikioi 4246 NOIP模拟赛Day2T1 奶牛的身高 题目描述 Description 奶牛们在FJ的养育下茁壮成长.这天,FJ给了奶牛Bessie一个任务,去看看每个奶牛场中若干只奶牛的 ...
- 《挑战程序设计竞赛》2.4 数据结构-并查集 POJ1182 2236 1703 AOJ2170
POJ1182 http://poj.org/problem?id=1182 题目 难得的中文题... 食物链 Time Limit: 1000MS Memory Limit: 10000K Tota ...
- 第三十四篇 玩转数据结构——哈希表(HashTable)
1.. 整型哈希函数的设计 小范围正整数直接使用 小范围负整数整体进行偏移 大整数,通常做法是"模一个素数" 2.. 浮点型哈希函数的设计 转成整型进行处理 3.. 字符串 ...
- 第三十二篇 玩转数据结构——AVL树(AVL Tree)
1.. 平衡二叉树 平衡二叉树要求,对于任意一个节点,左子树和右子树的高度差不能超过1. 平衡二叉树的高度和节点数量之间的关系也是O(logn) 为二叉树标注节点高度并计算平衡因子 AVL ...
随机推荐
- MyBatis mapper文件中使用常量
MyBatis mapper文件中使用常量 Java 开发中会经常写一些静态常量和静态方法,但是我们在写sql语句的时候会经常用到判断是否等于 //静态类 public class CommonCod ...
- Java-天平称重
题目: 用天平称重时,我们希望用尽可能少的砝码组合称出尽可能多的重量.如果只有5个砝码,重量分别是1,3,9,27,81 则它们可以组合称出1到121之间任意整数重量(砝码允许放在左右两个盘中).本题 ...
- Arduino上搭建ESP8266环境
我尝试了各种方法都无法在Arduino上安装ESP8266的环境,最后发现离线安装最稳妥. 1. 下载安装包,提取码:pktw 2. 将安装包内所有文件拷贝到C:\Users\Administrato ...
- P3206 [HNOI2010]城市建设 [线段树分治+LCT维护动态MST]
Problem 这题呢 就边权会在某一时刻变掉-众所周知LCT不支持删边的qwq- 所以考虑线段树分治- 直接码一发 如果 R+1 这个时间修改 那就当做 [L,R] 插入了一条边- 然后删的边和加的 ...
- how to use bookdown
模板下载 {#moban} 谢益辉 通用模板:https://github.com/rstudio/bookdown-demo 李东风中文模板: http://www.math.pku.edu.cn/ ...
- LOJ#508. 「LibreOJ NOI Round #1」失控的未来交通工具
题意 一个带边权无向图,有两种操作:加边以及询问在\(x,x+b,...,x+(c-1)b\)这些数中,有多少个数存在至少一条与之模\(m\)同余的从\(u\)到\(v\)的路径(可以不是简单路径). ...
- 咸鱼的ACM之路:DFS水题集
DFS的核心就是从一种状态出发,转向任意的一个可行状态,直到达到结束条件为止.(个人理解) 下面全是洛谷题,毕竟能找到测试点数据的OJ我就找到这一个....在其他OJ上直接各种玄学问题... P159 ...
- 500kuai
https://www.bilibili.com/bangumi/media/md11653495/?spm_id_from=666.10.b_62616e67756d695f64657461696c ...
- 使用nohup不产生log文件方法
思想 无法阻止nohup产生日志可以将其定向到空文件实现 实现 $ nohup xxx >/dev/null 2>&1 &
- c++ STL vector初步学习
/*vector(向量):是一种顺序容器,,动态数组,事实上和数组差不多,但它比数组更优越.一般来说数组不能动态拓展,因此在程序运行的时候不是浪费内存,就是造成越界.而vector正好弥补了这个缺陷, ...