并查集
作者:互联网
1. 简介
- 并查集(Union Find)也叫做不相交集合
- 并查集有两个核心操作
- 查找(Find):查找元素所在集合
- 合并(Union):将两个元素所在集合合并为一个集合
- 两种常见实现思路
- QuickFind:查找 —— O(1),合并 —— O(n)
- QuickUnion(常用):查找 —— O(logn)可优化至O(5),合并 —— O(logn)可优化至O(5)
2. 并查集如何存储数据
以存储整形数据为例,可以用数组实现并查集。比如数组的索引表示数据,值代表所属集合。
3. 实现
3.1 初始化
private int[] parents;
public UnionFind(int capacity){
parents = new int[capacity];
//每个数据单独属于一个集合
for(int i=0;i<parents.length;i++){
parents[i] = i;
}
}
3.2 基于QuickFind的Union、Find操作
union(1,0)是指将1的父结点改为0的父结点,即将1所在集合并入0所在集合。
public void union(int v1, int v2){
int p1 = find(v1);
int p2 = find(v2);
if(p1 == p2){
return;
}
for (int i = 0; i < parents.length; i++) {
if(parents[i] == p1){
parents[i] = p2;
}
}
}
public int find(int v){
return parents[v];
}
这种情况下实现的并查集,其相应的树的高度最多为2,所以查找时间短
3.3 基于QuickUnion
3.3.1 Union、Find操作
union(1,0)是指将1的根结点改为0的父结点,即将1所在集合并入0所在集合。
public void union(int v1, int v2){
int p1 = find(v1);
int p2 = find(v2);
if(p1 == p2){
return;
}
parents[p1] = p2;
}
public int find(int v){
while(v != parents[v]){
v = parents[v];
}
return v;
}
3.3.2 Union、Find操作的优化
-
优化点:在合并过程中可能会出现树不平衡的状况,甚至退化成链表
-
方案
- 基于size:元素少的树 嫁接到 元素多的树
int[] sizes = new int[capacity]; Arrays.fill(sizes,1); public void union_s1(int v1, int v2){ int p1 = find(v1); int p2 = find(v2); if(p1 == p2){ return; } if(sizes[p1] < sizes[p2]){ parents[p1] = p2; sizes[p2] += sizes[p1]; }else{ parents[p2] = p1; sizes[p1] += sizes[p2]; } }
- 基于rank: 矮的树 嫁接到 高的树
int[] ranks= new int[capacity]; Arrays.fill(ranks,1); public void union_s2(int v1, int v2){ int p1 = find(v1); int p2 = find(v2); if(p1 == p2){ return; } if(ranks[p1] < ranks[p2]){ ranks[p1] = p2; }else if(ranks[p1] > ranks[p2]){ ranks[p2] = p1; }else{ parents[p1] = p2; ranks[p2]++; } }
-
路径压缩
- 随依然有了基于rank的优化,树相对会平衡些,但随着Union的次数增多,树的高度依然会越来越高,这就导致了find操作时间复杂度变高
- 使用路径压缩可解决这一问题。路径压缩是指,在find()时使路径上的所有结点都指向根节点,从而降低树的高度
public int find_s(int v){ if(parents[v] != v){ parents[v] = find(parents[v]); } return parents[v]; }
-
更优的做法
虽然路径压缩可以降低树的高度,但是由于调用了递归,在优化树的高度过程中依然会付出一些代价。接下来介绍的两种方法,在降低树的高度的同时有着更小的代价。- 路径分裂:是路径上的每个结点都指向其祖父节点
public int find_s1(int v){ while(v != parents[v]){ int p = parents[v]; parents[v] = parents[p]; v = p; } return v; }
- 路径减半:是路径上每隔一个结点就指向其祖父节点
public int find_s2(int v){ while(v != parents[v]){ parents[v] = parents[parents[v]]; v = parents[v]; } return v; }
- 路径分裂:是路径上的每个结点都指向其祖父节点
3.4 isSame操作,判断是否属于同一集合
public boolean isSame(int v1, int v2){
int p1 = find(v1);
int p2 = find(v2);
return p1==p2;
}
标签:p2,p1,int,查集,public,parents,find 来源: https://blog.csdn.net/qq_42451178/article/details/113448044