第三十一篇 玩转数据结构——并查集(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 ...
随机推荐
- PC远程传文件到树莓派(PSCP详细版)
1.下载pscp软件 下载地址:http://www.pc6.com/softview/SoftView_456976.html 百度云下载地址:https://pan.baidu.com/s/1bZ ...
- @Value注解没有起作用的梳理
今天在使用@Value注解的时候遇到其不起作用的现象,先把场景说明一下:现在有A类和B类,而A类对象是通过new操作生成的临时对象,而B类对象是在A类中使用的:调试步骤如下: (1)将B类的属性字段都 ...
- PAT (Basic Level) Practice (中文)1043 输出PATest (20 分)
给定一个长度不超过 1 的.仅由英文字母构成的字符串.请将字符重新调整顺序,按 PATestPATest.... 这样的顺序输出,并忽略其它字符.当然,六种字符的个数不一定是一样多的,若某种字符已经输 ...
- PAT (Basic Level) Practice (中文)1057 数零壹 (20 分) (按行输入带空格的字符串)
给定一串长度不超过 1 的字符串,本题要求你将其中所有英文字母的序号(字母 a-z 对应序号 1-26,不分大小写)相加,得到整数 N,然后再分析一下 N 的二进制表示中有多少 0.多少 1.例如给定 ...
- C语言 while
C语言 while while 语句 流程图 案例 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stri ...
- js数据类型1
1. 分类(2大类) 基本(值)类型--5种 Number: 任意数值 String: 任意文本 Boolean: true/false undefined: undefined null: null ...
- 论文阅读笔记(七)【TIP2018】:Video-Based Person Re-Identification by Simultaneously Learning Intra-Video and Inter-Video Distance Metrics
是由一篇 IJCAI2016 扩的期刊. 该篇会议论文的阅读笔记[传送门] 期刊扩充的部分:P-SI2DL 1.问题描述: 在会议论文中介绍的SI2DL方法采用了视频三元组作为视频关系(是否匹配)的逻 ...
- php 获取视频时长
利用ffmpeg只能获取到本地视频的信息. function video_time($file) { ob_start(); passthru(sprintf(FFMPEG_PATH, $file)) ...
- Git操作时遇到的一些问题和相应的处理方式
Q1:如何解决冲突/避免冲突 A1:执行git fetch之后,本地可能会存在冲突. 如果希望合并本地修改内容,需要执行git merge.不过当有修改内容未提交时,不能merge,要么把修改内容提交 ...
- Linux运维---16. Kolla部署OpenStack外接rabbit集群
# 前提时rabbit集群已经搭建完成 vim /etc/kolla/globals.yml enable_rabbitmq: "no" rpc_transport_url: &q ...