Java并查集设计以及路径压缩实现

Java全能学习+面试指南:https://javaxiaobear.cn

并查集是一种树型的数据结构 ,并查集可以高效地进行如下操作:

  • 查询元素p和元素q是否属于同一组
  • 合并元素p和元素q所在的组

1、并查集的结构

并查集也是一种树型结构,但这棵树跟我们之前讲的二叉树、红黑树、B树等都不一样,这种树的要求比较简单:

  1. 每个元素都唯一的对应一个结点;
  2. 每一组数据中的多个元素都在同一颗树中;
  3. 一个组中的数据对应的树和另外一个组中的数据对应的树之间没有任何联系;
  4. 元素在树中并没有子父级关系的硬性要求;

2、并查集的API设计与实现

1、API设计

类名UnionFind
构造方法UF(int N):初始化并查集,以整数标识(0,N-1)个结点
成员方法public int count():获取当前并查集中的数据有多少个分组
public boolean connected(int p,int q):判断并查集中元素p和元素q是否在同一分组中
public int find(int p):元素p所在分组的标识符
public void union(int p,int q):把p元素所在分组和q元素所在分组合并
成员变量private int[] eleAndGroup: 记录结点元素和该元素所在分组的标识
private int count:记录并查集中数据的分组个数

2、实现

1、UF(int N)构造方法实现
  1. 初始情况下,每个元素都在一个独立的分组中,所以,初始情况下,并查集中的数据默认分为N个组;
  2. 初始化数组eleAndGroup;
  3. 把eleAndGroup数组的索引看做是每个结点存储的元素,把eleAndGroup数组每个索引处的值看做是该结点所在的分组,那么初始化情况下,i索引处存储的值就是i
2、union(int p,int q)合并方法实现
  1. 如果p和q已经在同一个分组中,则无需合并
  2. 如果p和q不在同一个分组,则只需要将p元素所在组的所有的元素的组标识符修改为q元素所在组的标识符即可
  3. 分组数量-1
3、代码实现
public class UnionFind {/*** 记录结点元素和该元素所在分组的标识*/private int[] eleAndGroup;/*** 记录并查集中数据的分组个数*/private int count;public UnionFind(int n) {//初始情况下,每个元素都在一个独立的分组中,所以,初始情况下,并查集中的数据默认分为N个组this.count = n;//初始化数组eleAndGroup = new int[n];//把eleAndGroup数组的索引看做是每个结点存储的元素,// 把eleAndGroup数组每个索引处的值看做是该结点所在的分组,// 那么初始化情况下,i索引处存储的值就是ifor (int i = 0; i < n; i++) {eleAndGroup[i] = i;}}/*** 获取当前并查集中的数据有多少个分组* @return*/public int count(){return count;}/*** 判断并查集中元素p和元素q是否在同一分组中* @param p* @param q* @return*/public boolean connected(int p,int q){return eleAndGroup[p] == eleAndGroup[q];}/*** 元素p所在分组的标识符* @param p* @return*/public int find(int p){return eleAndGroup[p];}/*** 把p元素所在分组和q元素所在分组合并* @param p* @param q*/public void union(int p,int q){//如果q和p已经在同一个分组中,不需要合并if(connected(p, q)){return;}//不在一个分组中int pFind = find(p);int qFind = find(q);for (int i = 0; i < eleAndGroup.length; i++) {if (eleAndGroup[i] == pFind){eleAndGroup[i] = qFind;}}//数量减1count--;}
}
  • 测试类

    public class UnionFindTest {public static void main(String[] args) {UnionFind uf = new UnionFind(5);int count = uf.count();System.out.println("总共有"+count+"个分组");Scanner scanner = new Scanner(System.in);while (true){System.out.println("请输入你要合并的第一个点");int i = scanner.nextInt();System.out.println("请输入你要合并的第二个点");int j = scanner.nextInt();if(uf.connected(i,j)){System.out.println("结点"+ i +"和结点"+ j +"已经在同一个组");continue;}uf.union(i,j);System.out.println("总共还有"+uf.count()+"个分组");}}
    }
    

3、并查集应用举例

如果我们并查集存储的每一个整数表示的是一个大型计算机网络中的计算机,则我们就可以通过connected(intp,int q)来检测,该网络中的某两台计算机之间是否连通?如果连通,则他们之间可以通信,如果不连通,则不能通信,此时我们又可以调用union(int p,int q)使得p和q之间连通,这样两台计算机之间就可以通信了。

一般像计算机这样网络型的数据,我们要求网络中的每两个数据之间都是相连通的,也就是说,我们需要调用很多次union方法,使得网络中所有数据相连,其实我们很容易可以得出,如果要让网络中的数据都相连,则我们至少要调用N-1次union方法才可以,但由于我们的union方法中使用for循环遍历了所有的元素,所以很明显,我们之前实现的合并算法的时间复杂度是O(N^2),如果要解决大规模问题,它是不合适的,所以我们需要对算法进行优化。

4、算法优化

为了提升union算法的性能,我们需要重新设计find方法和union方法的实现,此时我们先需要对我们的之前数据结构中的eleAndGourp数组的含义进行重新设定:

  • 我们仍然让eleAndGroup数组的索引作为某个结点的元素;
  • eleAndGroup[i]的值不再是当前结点所在的分组标识,而是该结点的父结点;

1、API设计

类名UF_Tree
构造方法UF_Tree(int N):初始化并查集,以整数标识(0,N-1)个结点
成员方法public int count():获取当前并查集中的数据有多少个分组
public boolean connected(int p,int q):判断并查集中元素p和元素q是否在同一分组中
public int find(int p):元素p所在分组的标识符
public void union(int p,int q):把p元素所在分组和q元素所在分组合并
成员变量private int[] eleAndGroup: 记录结点元素和该元素的父结点
private int count:记录并查集中数据的分组个数

2、实现

1、find(int p)查询方法实现
  1. 判断当前元素p的父结点eleAndGroup[p]是不是自己,如果是自己则证明已经是根结点了;
  2. 如果当前元素p的父结点不是自己,则让p=eleAndGroup[p],继续找父结点的父结点,直到找到根结点为止;
2、union(int p,int q)合并方法实现
  1. 找到p元素所在树的根结点
  2. 找到q元素所在树的根结点
  3. 如果p和q已经在同一个树中,则无需合并;
  4. 如果p和q不在同一个分组,则只需要将p元素所在树根结点的父结点设置为q元素的根结点即可;
  5. 分组数量-1

3、完成代码

public class UF_Tree {/*** 记录结点元素和该元素所在分组的标识*/private int[] eleAndGroup;/*** 记录并查集中数据的分组个数*/private int count;public UF_Tree(int n) {//初始情况下,每个元素都在一个独立的分组中,所以,初始情况下,并查集中的数据默认分为N个组this.count = n;//初始化数组eleAndGroup = new int[n];//把eleAndGroup数组的索引看做是每个结点存储的元素,// 把eleAndGroup数组每个索引处的值看做是该结点所在的分组,// 那么初始化情况下,i索引处存储的值就是ifor (int i = 0; i < n; i++) {eleAndGroup[i] = i;}}/*** 获取当前并查集中的数据有多少个分组* @return*/public int count(){return count;}/*** 判断并查集中元素p和元素q是否在同一分组中* @param p* @param q* @return*/public boolean connected(int p,int q){return eleAndGroup[p] == eleAndGroup[q];}/*** 元素p所在分组的标识符* @param p* @return*/public int find(int p){while (true){//判断当前元素p的父结点eleAndGroup[p]是不是自己,如果是自己则证明已经是根结点了;if(p == eleAndGroup[p]){return p;}//如果当前元素p的父结点不是自己,则让p=eleAndGroup[p],继续找父结点的父结点,直到找到根结点为止;p = eleAndGroup[p];}}/*** 把p元素所在分组和q元素所在分组合并* @param p* @param q*/public void union(int p,int q){//不在一个分组中int pFind = find(p);int qFind = find(q);if (qFind == pFind){return;}//如果p和q不在同一个分组,则只需要将p元素所在树根结点的父结点设置为q元素的根结点即可;eleAndGroup[pFind] = qFind;//数量减1count--;}
}
  • 测试类

    public class UnionFindTreeTest {public static void main(String[] args) {UF_Tree uf = new UF_Tree(5);int count = uf.count();System.out.println("总共有"+count+"个分组");Scanner scanner = new Scanner(System.in);while (true){System.out.println("请输入你要合并的第一个点");int i = scanner.nextInt();System.out.println("请输入你要合并的第二个点");int j = scanner.nextInt();if(uf.connected(i,j)){System.out.println("结点"+ i +"和结点"+ j +"已经在同一个组");continue;}uf.union(i,j);System.out.println("总共还有"+uf.count()+"个分组");}}
    }
    

5、路径压缩

UF_Tree中最坏情况下union算法的时间复杂度为O(N^2),其最主要的问题在于最坏情况下,树的深度和数组的大小一样,如果我们能够通过一些算法让合并时,生成的树的深度尽可能的小,就可以优化find方法。

之前我们在union算法中,合并树的时候将任意的一棵树连接到了另外一棵树,这种合并方法是比较暴力的,如果我们把并查集中每一棵树的大小记录下来,然后在每次合并树的时候,把较小的树连接到较大的树上,就可以减小树的深度。

只要我们保证每次合并,都能把小树合并到大树上,就能够压缩合并后新树的路径,这样就能提高find方法的效率。为了完成这个需求,我们需要另外一个数组来记录存储每个根结点对应的树中元素的个数,并且需要一些代码调整数组中的值。

1、API设计

类名UF_Tree_Weighted
构造方法UF_Tree_Weighted(int N):初始化并查集,以整数标识(0,N-1)个结点
成员方法public int count():获取当前并查集中的数据有多少个分组
public boolean connected(int p,int q):判断并查集中元素p和元素q是否在同一分组中
public int find(int p):元素p所在分组的标识符
public void union(int p,int q):把p元素所在分组和q元素所在分组合并
成员变量private int[] eleAndGroup: 记录结点元素和该元素的父结点
private int[] sz: 存储每个根结点对应的树中元素的个数
private int count:记录并查集中数据的分组个数

2、实现

public class UFTreeWeighted {private int[] eleAndGroup;private int[] rootSize;private int count;public UFTreeWeighted(int count) {this.count = count;//初始化数组eleAndGroup = new int[count];rootSize = new int[count];/*** 把eleAndGroup数组的索引看做是每个结点存储的元素,* 把eleAndGroup数组每个索引处的值看做是该结点所在的分组,* 那么初始化情况下,i索引处存储的值就是i*/for (int i = 0; i < count; i++) {eleAndGroup[i] = i;}//把sz数组中所有的元素初始化为1,默认情况下,每个结点都是一个独立的树,每个树中只有一个元素for (int i = 0; i < count; i++) {rootSize[i] = 1;}}/*** 获取当前并查集中的数据有多少个分组* @return count*/public int count(){return count;}/*** 判断并查集中元素p和元素q是否在同一分组中* @param p* @param q* @return*/public boolean connected(int p,int q){return find(p) == find(q);}/*** 元素p所在分组的标识符* @param p* @return*/public int find(int p){while (true){if(p == eleAndGroup[p]){return p;}p = eleAndGroup[p];}}/*** :把p元素所在分组和q元素所在分组合并* @param p* @param q*/public void union(int p,int q){//找到p元素的根结点int pRoot = find(p);//找到q元素的根结点int qRoot = find(q);//如果已经在一个组中,无需合并if(pRoot == qRoot){return;}//不在一个组中,比较q所在树中的元素个数和p所在树中的元素个数,小树向大树合并if(rootSize[pRoot] < rootSize[qRoot]){eleAndGroup[pRoot] = qRoot;rootSize[qRoot] += rootSize[pRoot];}else {eleAndGroup[qRoot] = pRoot;rootSize[pRoot] += rootSize[qRoot];}//分组数组-1count--;}
}

在这里插入图片描述

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

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

相关文章

uniapp 如何使用echarts 以及解决tooltip自定义不生效问题

使用的是echarts-for-wx插件&#xff1b; 正常写法案例&#xff1a;给tooltip数值加个% <template><view><uni-ec-canvas class"uni-ec-canvas"id"uni-ec-canvas"ref"canvas"canvas-id"uni-ec-canvas":ec"ec&quo…

【数据库】sql优化有哪些?从query层面和数据库层面分析

目录 归纳sql本身的优化数据库层面的优化 归纳 这类型问题可以称为&#xff1a;Query Optimization&#xff0c;从清华AI4DB的paper list中&#xff0c;该类问题大致可以分为&#xff1a; Query RewriterCardinality EstimationCost EstimationPlan Optimization 从中文的角…

SkipList 的索引过程,能否越两级搜索

“SkipList 的索引过程&#xff0c;能否越两级搜索&#xff1f;” 昨天&#xff0c;一个工作 7 年的粉丝&#xff0c;去某外包公司面试&#xff0c;被问到这个问题不知道该怎么回答。 今天正好有空&#xff0c;给大家分享一下这个问题的回答思路。 对了&#xff0c;这个问题…

ZooKeeper 实战(四) Curator Watch事件监听

文章目录 ZooKeeper 实战(四) Curator Watch事件监听0.前言1.Watch 事件监听概念2.NodeCache2.1.全参构造器参数2.2.代码DEMO2.3.日志输出 3.PathChildrenCache3.1.全参构造器参数3.2.子节点监听时间类型3.2.代码DEMO 4.TreeCache4.1.构造器参数4.2.代码DEMO4.3.日志输出 ZooKe…

Flink(十二)【容错机制】

前言 最近已经放假了&#xff0c;但是一直在忙一个很重要的自己的一个项目&#xff0c;用 JavaFX 和一个大数据组件联合开发一个功能&#xff0c;也算不枉我学了一次 JavaFX&#xff0c;收获很大&#xff0c;JavaFX 它作为一个 GUI 开发语言&#xff0c;本质还是 Java&#xff…

MSF流量加密

1、背景介绍 在MSF中生成shell&#xff0c;并上线运行时。都是通过http https tcp等协议传输。虽然MSF本身会对流量进行加密&#xff0c;但MSF太出名以致于其加密特征容易被IPS&#xff0c;WAF等可以检测带有攻击的特征的设备拦截或记录。 2、生成 SSL 证书 openssl req -x50…

关于运维·关于数据库面试题

目录 一、数据库类型 二、数据库引擎 三、mysql数据库类型 四、mysql的约束添加 五、主从复制原理 六、主从方式有几种 七、mysql主从数据不一致的原因 八、mysql的优化 九、什么是事务的特征 十、数据库读写分离的好处 十一、怎样优化sql语句 十二、mysql的同步方…

谷粒商城-商品服务-品牌管理-阿里云云存储+JSR303数字校验+统一异常处理

阿里云云存储OSS 分布式系统上传文件 分布式系统上传文件 单体应用上传&#xff1a;上传文件到服务器&#xff0c;想获取文件时再向服务器发请求获取文件。 分布式系统上传&#xff1a; 因为有多台服务器&#xff0c;为防止负载均衡导致获取文件时没找到对应的服务器&#xf…

实用编程调试技巧

目录 一、调试的基本步骤 二、Debug和Release的介绍 三、Windows环境调试介绍 1.调试环境的准备 2.学会快捷键 最常用的几个快捷键&#xff1a; 断点应用举例&#xff1a; 3.调试的时候查看程序当前信息 &#xff08;1&#xff09…

GitHub注册新账号的操作流程(详细)

目录 第一步 进入官网&#xff0c;点击右上角的"Sign up" 第二步 输入email地址 第三步 设置密码 第四步 输入昵称 第五步 根据个人喜好决定要不要接收GitHub的邮件推送。然后回答他们的验证问题 第六步 输入验证码 我在注册github账号时遇到过一些阻碍&#x…

软件测试|教你使用Python绘制正多边形

简介 绘制正多边形是Python图形编程的基本任务之一。在本文中&#xff0c;我将为你提供一个使用Python绘制正多边形的详细教程&#xff0c;并提供一个示例代码。我们将使用Python的Turtle库来进行绘制。 步骤1&#xff1a;导入Turtle库 我们需要先安装好Python环境&#xff…

Shiro框架:Shiro内置过滤器源码解析

目录 1. 常见项目中过滤器配置 2.Url访问控制配置解析为内置过滤器 2.1 DefaultFilterChainManager构造并注册内置过滤器 2.2 构造过滤器链 3. Shiro内置过滤器解析 3.1 内置过滤器概览 3.2 公共继承类解析 3.2.1 顶层Filter接口 3.2.2 AbstractFilter 3.2.3 Nameab…

二十几种未授权访问漏洞合集

未授权访问漏洞是一个在企业内部非常常见的问题&#xff0c;这种问题通常都是由于安全配置不当、认证页面存在缺陷&#xff0c;或者压根就没有认证导致的。当某企业对外的服务端口、功能无限制开放&#xff0c;并且对用户的访问没有做任何限制的时候&#xff0c;可能会泄露出某…

PLC数组队列搜索FC(SCL代码+梯形图程序)

根据输入数据搜索输入数据队列中和输入数据相同的数,函数返回其所在队列的位置。这里我们需要用到博途PLC的数组指针功能,有关数组指针的详细使用方法,可以参考下面文章: 博途PLC数组指针: https://rxxw-control.blog.csdn.net/article/details/134761364 区间搜索FC …

常用计算电磁学算法特性与电磁软件分析

常用计算电磁学算法特性与电磁软件分析 参考网站&#xff1a; 计算电磁学三大数值算法FDTD、FEM、MOM ADS、HFSS、CST 优缺点和应用范围详细教程 ## 基于时域有限差分法的FDTD的计算电磁学算法&#xff08;含Matlab代码&#xff09;-框架介绍 参考书籍&#xff1a;The finite…

【python】06.函数和模块的使用

函数和模块的使用 在讲解本章节的内容之前&#xff0c;我们先来研究一道数学题&#xff0c;请说出下面的方程有多少组正整数解。 事实上&#xff0c;上面的问题等同于将8个苹果分成四组每组至少一个苹果有多少种方案。想到这一点问题的答案就呼之欲出了。 可以用Python的程序来…

Spring Boot 整合支付宝实现在线支付方案(沙箱环境)

文章目录 1.理解沙箱环境2.沙箱环境接入准备2.1 访问开发者控制台2.2 获取重要信息2.3 处理秘钥 3.接入支付宝支付的流程4.实现支付4.1 添加 SDK 依赖4.2 创建配置类4.3 支付宝订单管理接口实现流程4.4 支付宝支付接口实现流程 5.支付宝支付功能演示7.总结 TIP&#xff1a;对于…

【UEFI基础】EDK网络框架(VLAN)

VLAN VLAN代码综述 在MNP中有很多的VLAN介绍&#xff0c;MNP存在的一个重要原因也是为了处理VLAN&#xff0c;而本文介绍的NetworkPkg\VlanConfigDxe\VlanConfigDxe.inf其实只是一个帮助模块&#xff0c;真正的VLAN配置还是在MNP中。 VLAN同样是一个UEFI Driver Model&#…

Redis实现分布式会话

Redis实现分布式会话 1 什么是分布式会话 1 这是我么之前学过的注册登录模式 2 如果非常多的人访问&#xff0c;因为单台服务器的访问承受能力是有限的&#xff0c;那么我们就想用多态服务器来承担压力 3 一般通过负载均衡的方式来实现&#xff0c;来分担服务器的压力。 4 负…

【PlantUML】- 时序图

写在前面 本篇文章&#xff0c;我们来介绍一下PlantUML的时序图。这个相对类图来讲&#xff0c;比较简单&#xff0c;也不需要布局。读完文章&#xff0c;相信你就能实际操作了。 目录 写在前面一、基本概念二、具体步骤1.环境说明2.元素3.语法4.示例 三、参考资料写在后面系列…