6种版本的并查集(java实现版)

目录

引入

并查集的具体讲解及代码实现

Quick Find

Quick Union

基于size的优化

代码实现

基于rank的优化

代码实现

路径压缩

代码实现

更多关于路径压缩的并查集


引入

由孩子指向父亲的这种特殊的树结构可以很高效的处理连接问题,在一个复杂的图中(如下图),给出图中任意两点,问它俩之间是否存在一条连接它俩的路径。

在并查集中,主要有合并集合以及判断是否连接两个动作,即“并”和“查”。

并查集的具体讲解及代码实现

//接口
public interface UF {//不考虑向并查集中添加元素或者删除元素int getSize();//判断是否相连boolean isConnected(int p, int q);//两个元素合并在一起,变成一个集合中的元素void unionElements(int p, int q);
}

Quick Find

相同id值的数字属于同一个集合,如下图,0、2、4、6、8是同一个集合,1、3、5、7、9是同一个集合。

我们用数组存储id值可以很容易的实现isConnected(int p, int q)这个“查”的操作,也就是转换成查找p、q的id值是否相等,即find(p) == find(q) ? 这个find操作的时间复杂度为O(1),非常快,我们叫做Quick Find。

“查”了解完了,那“并”呢?如果我们想合并1和4又该怎么操作呢。原本1 3 5 7 9是连接起来的,0 2 4 6 8是连接的,一旦我们把1和4连接起来,那么所有和1、4连接的元素也就都连接起来了。在我们上图的例子中我们所有的元素所对应的id值就应该改成一样的,都取0或1,具体都改成0还是都改成1是无所谓的,在这里我们选择都改为1。

我们通过循环的方式把0都改为1,这个Union的操作时间复杂度为O(n) 。

//第一版的并查集
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;}}@Override public int getSize(){return id.length;}//查找元素p所对应的集合的编号//由于这个方法不在接口中,所以我们设为private的private int find(int p){if(p < 0 || p >= id.length){throw new IllegalArgumentException("p is out of bound.");}return id[p];}@Overridepublic boolean isConnected(int p, int q){return find(p) == find(q);}@Overridepublic 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 Union

这一版的并查集是一般情况下更常用的。由于Quick Find中的合并操作时间复杂度为O(n),所以数据规模一大的话算法就会很慢,而下面要讲的这种并查集顾名思义可以很快的合并元素,因为我们要实现一种底层逻辑,即孩子指向父亲的特殊树结构。

如上图,每个结点只会有一个指针指向另一个结点,结点1、2、3是相连接的,根节点为2,指向它本身。结点5、6、7是相连的,根节点为5,现在我们希望合并3和7,那么只需要将两个集合的根节点相连就好,如下图。 

关于指针的存储,我们依然可以采取数组的方式来存储。数组就代表第i个元素的指针指向了某个结点。在初始情况下,每一个结点都是一个根节点,指向自己,没和任意元素相连。此刻不是一个树结构,而是一个森林。

如果我们想合并4、3该怎么做呢? 很简单,就让4的指针指向3。parent数组中4的位置改为3。

倘若要合并两个树结构中的元素,需要这两个树结构的根节点指向根节点, 这时候就要查找哪个是根节点,即这两棵树中哪个节点是自己指向自己的。比如我们要合并9、4。

我们为什么不让9直接指向4呢?是因为如果那样做就会形成一个链表,我们树结构的优越性就不存在了。 而如果9指向4的根节点,下次我们如果要查询9这个元素所对应的根节点只需要一步查询就好了。(别忘了把parent(9) = 8~)

//第二版并查集
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;}}@Overridepublic int getSize(){return parent.length;}//时间复杂度为O(h),h是树的高度//查找到数据所在的树相应的根节点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;}//时间复杂度为O(h),h是树的高度@Overridepublic boolean isConnected(int p, int q){return find(p) == find(q);}//时间复杂度为O(h),h是树的高度@Overridepublic void unionElements(int p, int q){int pRoot = find(p);int qRoot = find(q);if(pRoot == qRoot){return;}parent[pRoot] = qRoot;}
}

基于size的优化

在某些情况下,我们不判断树的形状就去合并会徒增树的高度形成一个链表的样子(如下图),我们可以考虑当前这棵树有多少个结点,即基于size来解决这个问题。

比如,我们要合并4和9。

如果我们让8指向9,深度就会为4。 

而我们让9指向8的话,树的高度只有3。

即我们让结点个数小的树指向结点个数多的树。

代码实现

//第三版的并查集
public class UnionFind3 implements UF{private int[] parent;private int[] sz; //sz[i]表示以i为根的集合中元素个数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;}}@Overridepublic int getSize(){return parent.length;}//时间复杂度为O(h),h是树的高度//查找到数据所在的树相应的根节点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;}//时间复杂度为O(h),h是树的高度@Overridepublic boolean isConnected(int p, int q){return find(p) == find(q);}//时间复杂度为O(h),h是树的高度@Overridepublic 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];}}
}

基于rank的优化

如果是基于size优化的话,结点少的指向结点多的,就会产生下图结果,树的高度变成了4。 

结点数少的树高度反而是高的,结点数多的这棵树高度却只有2。 所以更合理的是让7指向8。

所以更合理的方法是记录一下以这个结点为根的树深度是多少,让深度比较低树指向深度比较高的树,这就是基于rank的优化。

代码实现

//第四版的并查集
public class UnionFind4 implements UF{private int[] parent;private int[] rank; //rank[i]表示以i为根的集合中树的层数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;}}@Overridepublic int getSize(){return parent.length;}//时间复杂度为O(h),h是树的高度//查找到数据所在的树相应的根节点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;}//时间复杂度为O(h),h是树的高度@Overridepublic boolean isConnected(int p, int q){return find(p) == find(q);}//时间复杂度为O(h),h是树的高度@Overridepublic 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[qRoot] = pRoot;rank[pRoot] += 1;}}
}

路径压缩

如上图所示相同效力的三种树,深度不同效率是不同的。路径压缩就是把一棵比较高的树变成比较矮的树。我们把路径压缩放在find方法中,比如我们要查找4的根节点,即find(4)。

我们可以使用图上那句代码, 即把p结点的父亲的父亲设为p结点的父亲,如下图。树的深度就降低了。

然后我们从2开始,以此类推,得到下图。 把深度从5降成3,这就是路径压缩。

代码实现

//第五版的并查集
public class UnionFind5 implements UF{private int[] parent;private int[] rank; //rank[i]表示以i为根的集合中树的层数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;}}@Overridepublic int getSize(){return parent.length;}//时间复杂度为O(h),h是树的高度//查找到数据所在的树相应的根节点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;}//时间复杂度为O(h),h是树的高度@Overridepublic boolean isConnected(int p, int q){return find(p) == find(q);}//时间复杂度为O(h),h是树的高度@Overridepublic 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[qRoot] = pRoot;rank[pRoot] += 1;}}
}

我们添加上路径压缩后,rank没有跟着维护深度没有问题,rank只是作为合并时的参考,它不作为树的高度值和深度值,这就是为什么它叫rank而不是depth或者length。

更多关于路径压缩的并查集

第六版本主要是为了下图的路径压缩操作一步到位,这样的方式我们要借助递归实现。

//第六版的并查集
public class UnionFind6 implements UF{private int[] parent;private int[] rank; //rank[i]表示以i为根的集合中树的层数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;}}@Overridepublic int getSize(){return parent.length;}//时间复杂度为O(h),h是树的高度//查找到数据所在的树相应的根节点private int find(int p){if(p < 0 || p >= parent.length){throw new IllegalArgumentException("p is out of bound.");}parent[p] = find(parent[p]);return parent[p];}//时间复杂度为O(h),h是树的高度@Overridepublic boolean isConnected(int p, int q){return find(p) == find(q);}//时间复杂度为O(h),h是树的高度@Overridepublic 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[qRoot] = pRoot;rank[pRoot] += 1;}}
}

第六版路径压缩比第五版性能上整体差一点点,递归需要额外的消耗,参加算法竞赛不要忘了路径压缩提高性能哦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/591551.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

音视频技术开发周刊 | 326

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 全球最强「开源版Gemini」诞生&#xff01;全能多模态模型Emu2登热榜&#xff0c;多项任务刷新SOTA 最强的全能多模态模型来了&#xff01;就在近日&#xff0c;智源研究院…

使用echarts的bmap配置项绘制区域轮廓遮罩

示例图 代码 <template><div id"map" style"width: 100%; height: 100vh"></div> </template><script> import * as echarts from "echarts"; import "echarts/extension/bmap/bmap"; export default…

华为交换机入门(六):VLAN的配置

VLAN&#xff08;Virtual Local Area Network&#xff09;即虚拟局域网&#xff0c;是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。VLAN内的主机间可以直接通信&#xff0c;而VLAN间不能直接互通&#xff0c;从而将广播报文限制在一个VLAN内。 VLAN 主要用来解决如何…

企业工商信息数据哪里获取?工商全量信息有什么渠道?

随着互联网的发展和普及&#xff0c;越来越多的企业选择在网上进行业务推广和品牌宣传。对于一些想要了解企业工商信息的用户来说&#xff0c;如何获取企业工商信息数据成了一个非常重要的问题。下面分享获取企业工商全量信息的渠道和方式&#xff1a; 首先&#xff0c;我们可以…

浏览器---善用的一些调试技巧

https://www.cnblogs.com/dasusu/p/17932742.html

记一次Oracle Cloud计算实例ssh恢复过程

#ssh秘钥丢失# &#xff0c; #Oracle Cloud# 。 电脑上的ssh秘钥文件不知道什么时候丢失了&#xff0c;直到用的时候才发现没有了&#xff0c;这下可好&#xff0c;Oracle Cloud的计算实例连不上了&#xff0c;这个实例只能通过ssh连接上去&#xff1a; 以下是解决步骤&#x…

如何在 VeriStand 中设置反射内存通道--5565PIORC

环境 硬件 cPCI-5565PIORC 软件 VeriStand 我正在设置我的反射内存 PXI 卡&#xff08;例如 cPCI-5565PIORC&#xff09;。 我可以在我的 PXI 系统之间使用反射内存发送/接收什么&#xff1f; 如何设置我的 PXI 系统之间共享的通道&#xff1f; 使用反射内存&#xff0c;您可…

梯度下降算法 寻找函数最小值 找最快下山路线 python写个梯度下降算法示例

梯度下降算法是一种用于寻找函数最小值的优化算法。 它在机器学习和深度学习中被广泛使用&#xff0c;特别是在训练神经网络时。我们可以通过一个简单的生活中的例子来理解它&#xff1a; 想象你在一座山上&#xff0c;需要找到最快的路线下山。你不能一眼看到最低点&#xf…

【深度学习下载大型数据集】快速下载谷歌云盘数据集

个人博客:Sekyoro的博客小屋 个人网站:Proanimer的个人网站 跑深度学习的时候,一些数据集比较大,比如60多个G,而且只是训练集. 然后这些数据是由某些实验室组采集的,并不像一些大公司搞的,一般都直接方法一些网盘中. 如果是谷歌网盘,本身通过代理也不麻烦,但是发现即使通过代…

计算机毕业设计——SpringBoot社区物业管理系统(附源码)

1&#xff0c; 概述 1.1 课题背景 近几年来&#xff0c;随着物业相关的各种信息越来越多&#xff0c;比如报修维修、缴费、车位、访客等信息&#xff0c;对物业管理方面的需求越来越高&#xff0c;我们在工作中越来越多方面需要利用网页端管理系统来进行管理&#xff0c;我们…

​iOS实时查看App运行日志

目录 一、设备连接 二、使用克魔助手查看日志 三、过滤我们自己App的日志 &#x1f4dd; 摘要&#xff1a; 本文介绍了如何在iOS iPhone设备上实时查看输出在console控制台的日志。通过克魔助手工具&#xff0c;我们可以连接手机并方便地筛选我们自己App的日志。 &#x1f4…

AndroidStudio导入程序、项目(教程)

目录 1. 首先解压压缩包&#xff0c;转为文件夹 2. 打开解压好的项目文件夹&#xff0c;删除.gradle和.idea这两个文件 3. 修改bulid.gradle文件&#xff0c;将gradle的版本型号改成自己的 (1) 传统结构 (2) 简洁结构 4. 打开android stdio软件&#xff0c;导入已经修改好…

【搜索引擎】elastic search核心概念

前言 本文不涉及ES的具体安装下载、操作、集群的内容&#xff0c;这部分内容会放在后面一篇文章中。本文只包含ES的核心理论&#xff0c;看完本文再去学ES的细节会事半功倍。 目录 1.由日志存储引出的问题 2.什么是ES&#xff1f; 3.ES的数据结构 4.ES的核心原理 5.联系作…

如何使用SeaFile搭建本地私有云盘并结合cpolar实现远程访问

文章目录 1. 前言2. SeaFile云盘设置2.1 SeaFile的安装环境设置2.2 SeaFile下载安装2.3 SeaFile的配置 3. cpolar内网穿透3.1 Cpolar下载安装3.2 Cpolar的注册3.3 Cpolar云端设置3.4 Cpolar本地设置 4.公网访问测试5.结语 1. 前言 现在我们身边的只能设备越来越多&#xff0c;…

系列三、下载 安装Nacos(单机版)

一、下载 & 安装Nacos&#xff08;单机版&#xff09; 1.1、下载 官网&#xff1a;https://github.com/alibaba/nacos/releases?page3 我分享的&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1-RNX1Jt3s4cwhWUzUqEHhg?pwdyyds 提取码&#xff1a;yyds 1.2、安…

Vuex(vue2中的状态机)

目录 Vuex state属性 getters属性 mutations属性 actions属性 modules属性 辅助函数 Vuex 状态管理模式 维护公共状态 公共数据 使用状态机模块维护状态 A组件中分发工作&#xff08;发起异步请求)--->获取数据--->提交突变(将数据提交给突变 ) 通过突变修改状态…

k8s的三种发布方式

三种常见的发布方式 应用程序升级面临最大挑战是新旧业务切换&#xff0c;将软件从测试的最后阶段带到生产环境&#xff0c;同时要保证系统不间断提供服务。而最为常见三种发布方式分别为&#xff1a;蓝绿发布&#xff0c;灰度发布和滚动发布。 三种发布方式的最终目的都是为了…

服务器监控软件夜莺部署(一)

文章目录 一、夜莺介绍1. 简介2. 相关网站 二、夜莺部署1. 部署架构2. Docker启动3. 配置数据源4. 内置仪表盘效果5. 时序指标效果 一、夜莺介绍 1. 简介 夜莺监控系统是一款专业的服务器监控软件&#xff0c;它可以帮助用户实时监测服务器的CPU、内存、磁盘利用率等。 夜莺监…

TecoGAN视频超分辨率算法

1. 摘要 对抗训练在单图像超分辨率任务中非常成功&#xff0c;因为它可以获得逼真、高度细致的输出结果。因此&#xff0c;当前最优的视频超分辨率方法仍然支持较简单的范数&#xff08;如 L2&#xff09;作为对抗损失函数。直接向量范数作损失函数求平均的本质可以轻松带来时…

设计模式之工厂设计模式【创造者模式】

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…