第三十一篇 玩转数据结构——并查集(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 ...
随机推荐
- python3-cookbook笔记:第三章 数字日期和时间
python3-cookbook中每个小节以问题.解决方案和讨论三个部分探讨了Python3在某类问题中的最优解决方式,或者说是探讨Python3本身的数据结构.函数.类等特性在某类问题上如何更好地使 ...
- Java出现次数最多的整数
描述 编写一个程序,读入一组整数,这组整数是按照从小到大的顺序排列的,它们的个数N也是由用户输入的,最多不会超过20.然后程序将对这个数组进行统计,把出现次数最多的那个数组元素值打印出来.如果有两个元 ...
- tensor的维度扩张的手段--Broadcasting
broadcasting是tensorflow中tensor维度扩张的最常用的手段,指对某一个维度上重复N多次,虽然它呈现数据已被扩张,但不会复制数据. 可以这样理解,对 [b,784]@[784,1 ...
- 获取redis cluster中所有节点的内存使用情况
需求:获取redis cluster集群中所有节点的内存使用情况. ip_port=`redis-cli -h $ -p $ -a abc123 -c cluster nodes | awk '{pr ...
- Nginx模块之ngx_http_proxy_module
ngx_http_proxy_module模块: 示例: location / { proxy_pass http://localhost:8000; proxy_set_header Host $h ...
- win10中安装与配置maven
原文链接:https://www.cnblogs.com/wkrbky/p/6350334.html Maven安装配置(Windows10) 想要安装 Apache Maven 在Windows 系 ...
- python3练习100题——004
继续做题-经过python3的测试 原题链接:http://www.runoob.com/python/python-exercise-example4.html 题目:输入某年某月某日,判断这一天是 ...
- react-父子子孙组件嵌套-context
方案一 import React from 'react' import ReactTypes from 'prop-types' /* // 最外层的父组件 export default class ...
- yii2 环境切换(开发,正式)
方式一,在web中index修改 开发环境配置 web目录index.php defined('YII_DEBUG') or define('YII_DEBUG', true); defined('Y ...
- ajax从jsp向servlet传值
function Delete(s){ xmlHttp=new XMLHttpRequest(); var url = "/emp/FindSpecial?selcol=" +s; ...