第三十一篇 玩转数据结构——并查集(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 ...
随机推荐
- 11maven的聚合
我们都知道项目之间的依赖首先要把被依赖的项目先安装install到maven的本地仓库中,然后在本项目中配置被依赖的仓库坐标,才能进行依赖通信. 例如: A项目 > B项目 A项目依赖B项目中 ...
- ESXI | ESXI6.7如何在网页端添加用户并且赋予不同的权限
一.首先添加一个用户 管理---用户---安全和用户 添加的新用户会显示在下面 二.给添加上的用户赋予对应权限(我这里演示赋予的是只读权限) 主机---操作---权限 三.测试登录 当用只赋予了只读权 ...
- C语言递归之二叉树的最小深度
题目描述 给定一个二叉树,找出其最小深度. 最小深度是从根节点到最近叶子节点的最短路径上的节点数量. 说明: 叶子节点是指没有子节点的节点. 示例 输入:[3,9,20,null,null,15,7] ...
- 剑指offer-基础练习-增删节点-链表
/* 链表基本操作: 插入节点和删除节点 */ /* 思路: 使用指向链表的头指针,这样在新插入节点后,头指针不会改变 */ struct ListNode{ int value; ListNode* ...
- ZOJ 4082 Little Sub and his Geometry Problem题解
题意 f(u,v):x小于等于u且y小于等于v的点才对f有贡献,每个这样的点贡献(u-x)+() 思路 =f(u_2,v_2)" class="mathcode" src ...
- react-react中的css+评论组件
一个小案例,巩固有状态组件和无状态组件的使用 通过for循环生成多个组件 数据: CommentList = [ { user: '张三', content: '哈哈,沙发' }, { user: ' ...
- Prettier - Code formatter使用
更多VSCode插件使用请访问:VSCode常用插件汇总 Prettier - Code formatter这是一款 格式化js.css代码插件,暂不解释. Prettier是什么? Prettier ...
- Luogu4316 | 绿豆蛙的归宿 (期望DP)
题目背景 随着新版百度空间的上线,Blog宠物绿豆蛙完成了它的使命,去寻找它新的归宿. 题目描述 给出一个有向无环图,起点为1终点为N,每条边都有一个长度,并且从起点出发能够到达所有的点,所有的点也都 ...
- 洛谷P1551 亲戚 (并查集模板题)
链接 https://www.luogu.org/problemnew/show/P1551 代码 #include<bits/stdc++.h> using namespace std; ...
- Extended Traffic LightOJ - 1074 spfa判断负环
//判断负环 在负环内的城市输出? #include <iostream> #include <queue> #include <cstdio> #include ...