第三十一篇 玩转数据结构——并查集(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 ...
随机推荐
- vue动画&过渡整理
- Java代码中特殊注释
Java代码中特殊注释 TODO: + 说明:标识处有功能代码待编写,待实现的功能在说明中会简略说明. FIXME: + 说明:标识处代码需要修正,甚至代码是错误的,不能工作,需要修复,如何修正会在说 ...
- Hyperparameter tuning
超参数调整 详细可以参考官方文档 定义 在拟合模型之前需要定义好的参数 适用 Linear regression: Choosing parameters Ridge/lasso regression ...
- svn error: "Previous operation has not finished; run 'cleanup' if it was interrupted"
出现这种问题,有几个原因 1.本身文件确实是锁住了 2.之前clean up 过很多次,但是每次都可能以失败告终,造成work queue存在缓存队列 3.svn lock 有lock记录 以下是简单 ...
- C# LINQ学习笔记二:LINQ标准查询操作概述
本笔记摘抄自:https://www.cnblogs.com/liqingwen/p/5801249.html,记录一下学习过程以备后续查用. “标准查询运算符”是组成语言集成查询 (LINQ) 模式 ...
- Dubbo-服务注册中心之AbstractRegistry
在dubbo中,关于注册中心Registry的有关实现封装在了dubbo-registry模块中.提供者(Provider)个消费者(Consumer)都是通过注册中心进行资源的调度.当服务启动时,p ...
- 【E20200102-1】centos 7 下vsftp的安装和配置
一.准备工作 1.1.服务器准备 操作系统:centos 7.x 关闭防火墙(firewall/iptables)和SELinux 参见笔记<[E20200101-1]Centos 7.x 关闭 ...
- php 时间 日期
获取月初与月末 /** * 获取当前月初与月末时间 * * */ $month =8; $year = 2019; $startDay = $year . '-' . $month . '-1'; $ ...
- 对one hot 编码的理解,sklearn. preprocessing.OneHotEncoder()如何进行fit()的?
查阅了很多资料,逐渐知道了one hot 的编码,但是始终没理解sklearn. preprocessing.OneHotEncoder()如何进行fit()的?自己琢磨了一下,后来终于明白是怎么回事 ...
- 跨站脚本(XSS)
1.1 XSS定义 XSS,即为(Cross Site Scripting),中文名为跨站脚本,是发生在目标用户的浏览器层面上的,当渲染DOM树的过程发生了不在预期内执行的JS代码时,就发生了XSS攻 ...