数据结构 ---[实现并查集(UnionFind)]
Posted 小智RE0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 ---[实现并查集(UnionFind)]相关的知识,希望对你有一定的参考价值。
刚开始学并查集之前,也是提前找了一点资料看看,
推荐看这位博主的文章;讲解并查集通俗易懂. 知乎博主:Pecco -->算法学习笔记(1) : 并查集
文章目录
可用并查集解决网络上的节点连接问题;具体说的话右两个功能;(1)判断两个点是否连接,(2)若没有连接就进行连接
首先在实现之前定义一个通用的接口UnionSet
;
在使用并查集时,并不关心元素内容,
因此可以使用数组保存元素,p和q分别对应元素所在数组的索引
/**
* @author by CSDN@小智RE0
* @date 2021-12-08
* 并查集通用接口
*/
public interface UnionSet
/**
* 定义的连接方法
* @param p 第一个集合的元素当前索引
* @param q 第二个集合的元素当前索引
*/
void toUnion(int p, int q);
/**
* 判断两个集合是否连接
* @param p 集合1的元素的当前索引
* @param q 集合2的元素的当前索引
* @return 是否连接
*/
boolean isConnection(int p, int q);
/**
* 返回当前集合的元素个数;
* @return 元素的个数
*/
int getSize();
📢实现方式1:使用数组基础实现
过程
比如这样,可以把数组中的每个元素都看做是单独的集合;
例如要查看节点1 和 4 是否连接; 查到节点1的数组元素内容是1,节点4的数组元素内容是0;不相等,所以没有连接;
那么,要对节点1和节点4进行连接,实际上就得把他们所属的集合合并起来
;
将节点4所属集合的所有集合元素都替换为1即可;
代码实现
import com.company.unionset.UnionSet;
/**
* @author by CSDN@小智RE0
* @date 2021-12-08
* 方式1:::数组基础实现并查集;
*/
public class RealUnionSet01 implements UnionSet
//存储元素的数组;
int[] data;
//初始化;
public RealUnionSet01(int len)
this.data = new int[len];
//向数组存入元素;
for (int i = 0; i < len; i++)
this.data[i] = i;
//用于查找当前索引的对应元素;
public int findEle(int index)
return this.data[index];
@Override
public void toUnion(int p, int q)
//找到这两个索引的对应元素;
int pEle = findEle(p);
int qEle = findEle(q);
if(pEle!=qEle)
//第一个集合的元素统一重置为第二个集合的;
for (int i = 0; i < this.data.length; i++)
if (findEle(i) == pEle)
this.data[i] = qEle;
@Override
public boolean isConnection(int p, int q)
//分别找到这两个索引的对应元素;
int pEle = findEle(p);
int qEle = findEle(q);
return pEle == qEle;
@Override
public int getSize()
return data.length;
测试1
/**
* @author by CSDN@小智RE0
* @date 2021-12-08
* 测试方式1实现的
*/
public class Test01
public static void main(String[] args)
int len = 100000;
int num = 100000;
RealUnionSet01 rs1 = new RealUnionSet01(len);
test1(rs1,num);
/**
* 在并查集下测试num次连接
* @param rs 并查集
* @param num 次数
*/
public static void test1(UnionSet rs, int num)
long startTime = System.nanoTime();
//使用随机数类;
Random rd = new Random();
for (int i = 0; i < num; i++)
int n1 = rd.nextInt(rs.getSize());
int n2 = rd.nextInt(rs.getSize());
rs.toUnion(n1,n2);
long endTime = System.nanoTime();
System.out.println("连接用时"+(endTime-startTime)/1000000000.0);
连接用时6.4901904
📢实现方式2:以数组为基础,树形结构实现
过程
刚开始每个节点都是自连接的;
若要将节点2和节点3进行连接,节点1还是自连接
然后将节点1与节点3连接,只要将节点1和节点3的根节点连接即可;
只要节点所在的那棵树的顶层节点相同,就认为这两棵树是连接的;
顶层节点的特点:::>>>它的父节点就是自己,
例如用数组作为底层结构,刚开始都是指向自己;每个节点的顶层节点都是自己;进行自连接;
用树结构来看的话,就是下面这样;
比如说要将
节点3和节点4进行连接
;将节点3作为节点4的顶级节点即可;
那么,要查看节点是否连接,看看该节点索引的元素是否相等即可;
将
节点3和节点8进行连接
;节点3的父节点改为节点8即可
将
节点6和节点5进行连接
;节点6的父节点改为节点5即可;
将
节点9和节点4进行连接
;节点9指向节点4的顶层节点;
将
节点2和节点1连接
,
将
节点5和节点0连接
;
将
节点7和节点2进行连接
;节点7连到节点2的父节点1上;
节点6和节点2进行连接
;将节点6的顶层节点0拼接到节点2的顶层节点1上
代码实现
import com.company.unionset.UnionSet;
/**
* @author by CSDN@小智RE0
* @date 2021-12-08
* 方式2::
*/
public class RealUnionSet02 implements UnionSet
//使用数组作为底层结构;就是存入父级节点的元素;
int[] parent;
//初始化;
public RealUnionSet02(int len)
this.parent = new int[len];
//初始化都是自连接;
for (int i = 0; i < len; i++)
this.parent[i] = i;
//辅助的方法;findParent;
private int findParent(int index)
while(index !=this.parent[index])
index = this.parent[index];
return index;
@Override
public void toUnion(int p, int q)
//分别找到这两个索引的对应顶级元素;
int pEle = findParent(p);
int qEle = findParent(q);
//若没有连接,就把两个索引下的元素置为同一个;
if(pEle!=qEle)
this.parent[pEle] = qEle;
@Override
public boolean isConnection(int p, int q)
//分别找到这两个索引的对应顶级元素;
int pEle = findParent(p);
int qEle = findParent(q);
return pEle == qEle;
@Override
public int getSize()
return this.parent.length;
测试2
public class Test02
public static void main(String[] args)
int len = 100000;
int num = 100000;
RealUnionSet02 rs2 = new RealUnionSet02(len);
test2(rs2,num);
/**
* 在并查集下测试num次连接
* @param rs 并查集
* @param num 次数
*/
public static void test2(UnionSet rs, int num)
long startTime = System.nanoTime();
//使用随机数类;
Random rd = new Random();
for (int i = 0; i < num; i++)
int n1 = rd.nextInt(rs.getSize());
int n2 = rd.nextInt(rs.getSize());
rs.toUnion(n1,n2);
long endTime = System.nanoTime();
System.out.println("连接用时"+(endTime-startTime)/1000000000.0);
连接用时3.321709
📢实现方式3:(优化方式2),添加一个辅助数组存储每棵树的节点个数
不管是查询操作还是合并操作,使用树结构形式的
并查集的复杂度为O(h),在极端的情况下,这棵树的深度会比较大。
比如在连接节点0和3时,这样直接挂接会增大树的深度;降低效率;
具体优化树的连接操作;将小树连接到大树上面;
将元素个数少的树合并到元素个数多的树上。需要开辟空间,存放每棵树的元素个数。
那么就得需要单独在创建出一个数组用来存储树的节点个数;
import com.company.unionset.UnionSet;
/**
* @author by CSDN@小智RE0
* @date 2021-12-09
* 方式3::(对实现方式2的优化)
*/
public class RealUnionSet03 implements UnionSet
//使用数组作为底层结构;就是存入父级节点的元素;
int[] parent;
//存储每棵树的节点个数;
int[] size;
//初始化;
public RealUnionSet03(int len)
this.parent = new int[len];
this.size = new int[len];
//初始化都是自连接;
for (int i = 0; i < len; i++)
this.parent[i] = i;
//初始化,每棵树都是单独自连接;
this.size[i] = 1;
//辅助的方法;findParent;
private int findParent(int index)
while(index !=this.parent[index])
index = this.parent[index];
return index;
//优化点;---->
@Override
public void toUnion(int p, int q)
//分别找到这两个索引的对应顶级元素;
int pEle = findParent(p);
int qEle = findParent(q);
//若没有连接,就把两个索引下的元素置为同一个;
if(pEle!=qEle)
//需要比较树的节点个数;把小树接到大树上;
if(size[pEle] >size[qEle])
parent[qEle] = pEle;
size[pEle] += size[qEle];
else
parent[pEle] = qEle;
size[qEle] += size[pEle];
@Override
public boolean isConnection(int p, int q)
//分别找到这两个索引的对应顶级元素;
int pEle = findParent(p);
int qEle = findParent(q);
return pEle == qEle;
@Override
public int getSize()
return this.parent.length;
📢实现方式4:(优化方式3) 按秩合并
方式三虽然进行小树连接到大树的操作,但是还有一个问题,可能会出现下面这种情况的树;
比如,经过一系列连接后,
现在要
连接节点4和节点2
;由于节点2所在的树结点比较多,就得让节点4所在的树合并到节点2所在的树;就出现这样情况了;也是会影响效率;
import com.company.unionset.UnionSet;
/**
* @author by CSDN@小智RE0
* @date 2021-12-09
* 方式4::(对实现方式3的优化) --> 对树的高度进行优化;
* 按秩合并;
*/
public class RealUnionSet04 implements UnionSet
//使用数组作为底层结构;就是存入父级节点的元素;
int[] parent;
//存储每棵树的高度;
int[] rank;
//初始化;
public RealUnionSet04(int len)
this.parent = new int[len];
this.rank = new int[len];
//初始化都是自连接;
for (int i = 0; i < len; i++)
this.parent[i] = i;
//初始化,每棵树都是单独自连接;
this.rank[i] = 1;
//辅助的方法;findParent;
private int findParent(int index)
while(index !=this.parent[index])
index = this.parent[index];
return index;
//优化点;---->
@Override
public void toUnion(int p, int q)
//分别找到这两个索引的对应顶级元素;
int pEle = findParent(p);
int qEle = findParent(q);
//若没有连接,就把两个索引下的元素置为同一个;
if (pEle != qEle)
//由低的树到高的树;
if (rank[pEle] > rank[qEle])
parent[qEle] = pEle; //注意高度不变;
else if (rank[pEle] < rank[qEle])
parent[pEle] = qEle;
else
//若相等;合并后,高度要变化;
parent[qEle] = pEle;
rank[pEle] += 1;
@Override
public boolean isConnection(int p, int q)
//分别找到这两个索引的对应顶级元素;
int pEle = findParent(p);
int qEle = findParent(q);
return pEle == qEle;
@Override
public int getSize()
return this.parent.length;
📢实现方式5:路径压缩
具体的过程,可以这么看,让节点4作为当前操作节点,把它挂接到父级节点3的父级节点2上;
然后再让节点2作为当前操作节点;让它挂接到父级节点1的父级节点0上;
import com.company.unionset.UnionSet;
/**
* @author by CSDN@小智RE0
* @date 2021-12-09
* 方式5::路径压缩法
*/
public class RealUnionSet05 implements UnionSet
//使用数组作为底层结构;就是存入父级节点的元素;
int[] parent;
//存储每棵树的高度;
int[] rank;
//初始化;
public RealUnionSet05(int len)
this.parent = new int[len];
this.rank = new int[len];
//初始化都是自连接;
for (int i = 0; i < len; i++)
this.parent[i] = i;
//初始化,每棵树都是单独自连接;
this.rank[i] = 1;
//辅助的方法;findParent; 优化点----->
private int findParentjava——并查集 UnionFind