【数据结构】--并查集

news/2024/4/28 4:07:36/文章来源:https://blog.csdn.net/m0_63077733/article/details/130029082

目录

一、概念

​编辑

二、应用场景--“连接”问题(属于同一Qu

三、实现思路

 四、如何存储数据

五、定义接口

1.初始化(init)

2.其他

isSame()

六、抽象类

六、Quick Find【v1 所在集合的所有元素都指向 v2 的根节点】

1.Union

1.Union图解

2.注意点: 

3.代码实现

2.find 

1.find图解

2.代码实现

3.完整代码

七、(常用)Quick Union【v1 的根节点指向 v2 的根节点】

1.Union

1.注意点

2.Quick Union图解

3.代码实现

2.find

代码实现

3.完整代码

八、Quick Union的优化

1.简介

 2.方案一:基于size的优化【元素少的树 嫁接到 元素多的树】

​编辑 

3.方案二:基于rank的优化【矮的树 嫁接到 高的树】

九、路径压缩(Path Compression)

1.什么是路径压缩?

2.代码实现

十、路径分裂(Path Spliting)

1.概念

 2.代码实现

十一、路径减半(Path Halving)

1.概念

2.代码实现

十二、总结


一、概念

并查集(Disjoint Set)是一种可以动态维护若干个不重叠的集合,并支持合并与查询两种操作的一种数据结构。按照一般的思路,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中,这样时间复杂度就太高啦。而并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。

并查集支持查找一个元素所属的集合以及两个元素各自所属的集合的合并等运算,当给出两个元素的一个无序对(a,b)时,需要快速“合并” a 和 b 分别所在的集合,这期间需要反复“查找”某元素所在的集合。“并”、“查”和“集” 3 个字由此而来。在这种数据类型中,n个不同的元素被分为若干组。每组是一个集合,这种集合叫分离集合,称之为并查集。

二、应用场景--“连接”问题(属于同一Qu

并查集是一种非常精巧而实用的数据结构,它注意用于处理一些不相交集合的合并温问题。一些常见的用途有连通子图,求最小生成树Kruskal算法和求最近公共祖先(LCA)。

三、实现思路

 四、如何存储数据

假设并查集处理的数据都是整型,那么可以用整型数组来存储数据。

  • 数组索引代表元素值
  • 索引对应的值代表这个元素的根节点

并查集是可以用数组实现的树形结构(二叉堆、优先级队列也是可以用数组实现的树形结构) 

五、定义接口

1.初始化(init)

初始化时,每个元素各自属于一个单元素集合

private int[] parents;
public UnionFind(int capacity){if(capacity < 0){throw new IllegalArgumentException("capacity must >= 1");}parents = new int[capacity];for (int i = 0; i < parents.length; i++) {parents[i] = i;}
}

2.其他

/*** 查找v所属的集合(根结点)*/
public abstract int find(int v);
/*** 合并v1、v2所在的集合*/
public abstract void union(int v1, int v2);
/*** 检查v1、v2是否属于同一集合*/
public boolean isSame(int v1, int v2);

isSame()

public boolean isSame(int v1, int v2){return find(v1) == find(v2);
}

六、抽象类

这是个并查集的抽象类,后面的所有并查集都将继承它。

package union;/*** 这是个并查集的抽象类,后面的所有并查集都将继承它。*/
public abstract class UnionFind {
//    初始一维数组存放父节点protected int[] parents;//初始化public UnionFind(int capacity){if (capacity<0){throw new IllegalArgumentException("capacity must be >=1");}
//        创建一个capacity的数组容量大小存放父节点parents=new int[capacity];//将初始化,自己作为父节点for (int i=0;i<parents.length;i++){parents[i]=i;}}public abstract void union(int v1,int v2);/*** 检查v1,v2是否属于同一个集合* @param v1* @param v2* @return*/public boolean isSame(int v1,int v2){return find(v1)==find(v2);}/*** 查找v所属的集合(根节点)* @param v* @return*/public abstract int find(int v);//    判断v是否越界protected void rangeCheck(int v){if (v<0 || v>=parents.length){throw new IllegalArgumentException("v is out of bounds");}}
}

六、Quick Find【v1 所在集合的所有元素都指向 v2 的根节点】

1.Union

1.Union图解

2.注意点: 

1)将v1的根节点修改成v2的根节点,同时要将v1的根节点的根节点全部修改为v2的根节点。

2)union 时间复杂度:O(n)

3)树的高度最高为2

3.代码实现

/*** 将v1所在集合的所有元素都嫁接到v2的父节点上* v1    v2   union(v1,v2)*  0    4	     4* 1 2   3     0 1 2 3*/
public void union(int v1, int v2){int p1 = parents[v1];int p2 = parents[v2];for (int i = 0; i < parents.length; i++) {if(parents[i] == p1){
//如果跟p1是同一个父节点,则将其父节点修改为p2的parents[i] = p2;}}
}

2.find 

1.find图解

2.代码实现

    /*** 查找v所属的集合(根节点)* @param v* @return*/public int find(int v){rangeCheck(v);return parents[v];}

3.完整代码

UnionFind_QuickFind 

package union;/*** 查找(Find)的时间复杂度:O(1)* 合并(Union)的时间复杂度:O(n)*/public class UnionFind_QuickFind extends UnionFind{public UnionFind_QuickFind(int capacity) {super(capacity);}/*** 查找v所属的集合(根节点)* @param v* @return*/public int find(int v){rangeCheck(v);return parents[v];}public void union(int v1,int v2){
//        p1:表示v1的父节点int p1=find(v1);
//        p2:表示v2的父节点int p2=find(v2);if (p1==p2) return;//        到此处则v1和v2不是同一个父节点for (int i=0;i<parents.length;i++){
//            遍历所有的父节点,如果该父节点和v1表示,要将其全部修改为v2的父节点(p2)if (parents[i]==p1){parents[i]=p2;}}}
}

七、(常用)Quick Union【v1 的根节点指向 v2 的根节点】

1.Union

1.注意点

1)Quick Find 的 union(v1, v2):让 v1 所在集合的所有元素都指向 v2 的根节点

2)Quick Union 的 union(v1, v2):让 v1 的根节点指向 v2 的根节点 。(与Quick Find进行对比)

3)树的最大高度超过2

2.Quick Union图解

 

3.代码实现

/*** 将v1的根节点嫁接到v2的根节点上*/@Overridepublic void union(int v1, int v2) {int p1=find(v1);int p2=find(v2);if (p1==p2){return;}
//        将p1指向p2的父节点【也就是p1的父节点为p2的父节点】parents[p1]=p2;}

2.find

当前节点值和父节点的值相等的时候,说明该节点是根节点【v==parents[i]】

代码实现

   /*** 通过parents链表不断向上查找,直到根节点* @param v* @return*/   @Overridepublic int find(int v) {rangeCheck(v);//当v不跟父节点的值相等,则表示还未找到根节点while (v!=parents[v]){v=parents[v];}return v;}

3.完整代码

QuionFind_QuickUnion
package union;/*** 查找(Find)的时间复杂度:O(logn), 可以优化至 O(𝛼(𝑛)), α(𝑛) < 5* 合并(Union)的时间复杂度:O(logn), 可以优化至 O(𝛼(𝑛)), α(𝑛) < 5*/public class QuionFind_QuickUnion extends UnionFind{public QuionFind_QuickUnion(int capacity) {super(capacity);}/*** 将v1所在的根节点 嫁接到v2的根节点上* @param v1* @param v2*/@Overridepublic void union(int v1, int v2) {int p1=find(v1);int p2=find(v2);if (p1==p2){return;}
//        将p1指向p2的父节点【也就是p1的父节点为p2的父节点】parents[p1]=p2;}/*** 通过parents链表不断向上查找,直到根节点* @param v* @return*/@Overridepublic int find(int v) {rangeCheck(v);//当v不跟父节点的值相等,则表示还未找到根节点while (v!=parents[v]){v=parents[v];}return v;}
}

八、Quick Union的优化

1.简介

 2.方案一:基于size的优化【元素少的树 嫁接到 元素多的树】

不是固定的让某一棵树嫁接到另一棵树,让元素少的树 嫁接到 元素多的树

package union;/*** Quick Union--基于size的优化*/
public class UnionFind_QuickUnion_Size extends UnionFind{
//    该数组存放以size[i]为根节点的节点个数private int[] sizes;public UnionFind_QuickUnion_Size(int capacity) {super(capacity);sizes=new int[capacity];for (int i=0;i<sizes.length;i++){
//            初始化一个根节点只有一个节点【就是它本身】sizes[i]=1;}}/*** 将v1所在的根节点 嫁接到v2的根节点上* @param v1* @param v2*/@Overridepublic void union(int v1, int v2) {int p1=find(v1);int p2=find(v2);if (p1==p2){return;}//如果v1所在的树的长度<v2所在的树的长度,则将v1嫁接到v2上if (sizes[p1]<sizes[p2]){parents[p1]=p2;sizes[p2]+=sizes[p1];}else { //如果v1所在的树的长度>v2所在的树的长度,则将v2嫁接到v1上parents[p2]=p1;sizes[p1]+=sizes[p2];}}/*** 通过parents链表不断向上查找,直到根节点* @param v* @return*/@Overridepublic int find(int v) {rangeCheck(v);//当v不跟父节点的值相等,则表示还未找到根节点while (v!=parents[v]){v=parents[v];}return v;}
}

 

3.方案二:基于rank的优化【矮的树 嫁接到 高的树】

package union;/*** Quick Union--基于rank的优化*/
public class UnionFind_QuickUnion_Rank extends UnionFind{
//    该数组存放以rank[i]为存放高度private int[] ranks;public UnionFind_QuickUnion_Rank(int capacity) {super(capacity);ranks=new int[capacity];for (int i=0;i<ranks.length;i++){
//            初始化一个根节点只有一个节点【就是它本身】ranks[i]=1;}}/*** 将v1所在的根节点 嫁接到v2的根节点上* @param v1* @param v2*/@Overridepublic void union(int v1, int v2) {int p1=find(v1);int p2=find(v2);if (p1==p2){return;}if(ranks[p1] < ranks[p2]){ //从矮的到高的树的总体高度不变parents[p1] = p2;}else if(ranks[p1] > ranks[p2]){parents[p2] = p1;}else{ // ranks[p1] == ranks[p2]//谁嫁接谁都可以parents[p1] = p2;//嫁接到p2ranks[p2] += 1;}}/*** 通过parents链表不断向上查找,直到根节点* @param v* @return*/@Overridepublic int find(int v) {rangeCheck(v);//当v不跟父节点的值相等,则表示还未找到根节点while (v!=parents[v]){v=parents[v];}return v;}
}

九、路径压缩(Path Compression)

虽然有了基于 rank 的优化,树会相对平衡一点,但是随着 union 次数的增多:树的高度依然会越来越高,导致 find 操作变慢,尤其是底层节点 (因为 find 是不断向上找到根节点) 。

1.什么是路径压缩

在 find 时使路径上的所有节点都指向根节点

 

2.代码实现

该类继承了 UnionFind_QU_R,表明它是在 Quick Union 的 rank 优化的基础上,再优化 

package union;/*** Quick Union - 基于rank的优化 - 路径压缩(Path Compression)*/
public class UnionFind_QuickUnion_rank_PathCompression extends UnionFind_QuickUnion_Rank{public UnionFind_QuickUnion_rank_PathCompression(int capacity) {super(capacity);}@Overridepublic int find(int v) { //v==1,parents[v]==2rangeCheck(v);if (parents[v]!=v){ //表示还没有找到根节点parents[v]=find(parents[v]);//将根节点赋值}return parents[v];}
}

十、路径分裂(Path Spliting)

1.概念

使路径上的每个节点都指向其祖父节点(parent的parent)

 2.代码实现

该类继承了 UnionFind_QU_R,表明它是在 Quick Union 的 rank 优化的基础上

package union;/*** Quick Union - 基于rank的优化 - 路径分裂(Path Spliting)*/
public class UnionFind_QuickUnion_rank_PathSpliting extends UnionFind_QuickUnion_Rank{public UnionFind_QuickUnion_rank_PathSpliting(int capacity) {super(capacity);}@Overridepublic int find(int v){rangeCheck(v);while(v != parents[v]){
//            将2保留下来,如果不保留,则2直接消失int p = parents[v];
//            parents[parents[v]]:找到v的祖父节点parents[v] = parents[parents[v]];v = p;}return parents[v];}
}

十一、路径减半(Path Halving)

1.概念

使路径上每隔一个节点就指向其祖父节点(parent的parent)

 

2.代码实现

该类继承了 UnionFind_QU_R,表明它是在 Quick Union 的 rank 优化的基础上,再优化

package union;/***  Quick Union - 基于rank的优化 - 路径减半(Path Halving)*/
public class UnionFind_QuickUnion_rank_PathHalving extends UnionFind_QuickUnion_Rank{public UnionFind_QuickUnion_rank_PathHalving(int capacity) {super(capacity);}@Overridepublic int find(int v){rangeCheck(v);while(v != parents[v]){parents[v] = parents[parents[v]];v = parents[v];}return v;}
}

十二、总结

 

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

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

相关文章

45-Dockerfile-ARG/ENV指令

AGR/ENV指令前言ARG作用格式说明生效范围使用示例ENV作用格式说明使用环境变量使用示例ARG 和 ENV 的区别前言 本篇来学习下Dockerfile中的AGR/ENV指令 ARG 作用 定义一个可以在构建镜像时使用的变量 格式 ARG <name>[<default value>]说明 在执行 docker b…

SpringBoot学习笔记(四)

SpringBoot整合quartz 任务 定时任务是企业级应用中的常见操作市面上流行的定时任务技术: Quartz、 Spring Task 相关概念: 工作(Job):用于定义具体执行的工作工作明细(JobDetail):用于描述定时工作相关的信息触发器(Trigger):用于描述触发工作的规则,通常使用cron表达式定…

Unity --- 3d数学 --- 坐标系统

1.世界坐标系是固定不动的 2.每一个游戏物体在世界坐标系中都有对应的坐标和方向 1.轴心点的位置不是固定的&#xff0c;是可以人为设定的 1.Screen Space --- 屏幕坐标 2.我们看到的屏幕其实就是相机所在的平面的位置 --- 而屏幕坐标系的Z其实就是游戏中的物体到相机平面的…

开源DataX集成可视化项目Datax-Web的使用

上一篇文章我们已经搭建好了 Datax-Web 后台&#xff0c;这篇文章我们具体讲一下如何通过Datax-Web来配置&#xff0c;同步MySQL数据库。 目标 MySql数据库全量同步 1.执行器配置 1、"调度中心OnLine:"右侧显示在线的"调度中心"列表, 任务执行结束后, 将会…

钢铁侠材质制作——2、线条轮廓部分的制作

钢铁侠Unlit光照Shader&#xff0c;三种效果变化返回目录大家好&#xff0c;我是阿赵&#xff0c;这里是钢铁侠材质制作第二部分&#xff0c;线条轮廓部分的制作 为了实现这个效果&#xff0c;可以把细节拆分成以下几个部分&#xff1a; 1、轮廓光 1.效果分析 这是一个很基…

C生万物 | 十分钟带你学会位段相关知识

结构体相关知识可以先看看这篇文章 —— 链接 一、什么是位段 位段的声明和结构是类似的&#xff0c;有两个不同&#xff1a; 位段的成员必须是 int、unsigned int 或signed int位段的成员名后边有一个冒号和一个数字 在下面&#xff0c;我分别写了一个结构体和一个位段&…

手动构建自己的docker容器镜像实战

前言 之前的实战中&#xff0c;我们实战中&#xff0c;我们使用的镜像都是镜像仓库已有的镜像。 已有的镜像都是别人已经开发好上传的。今天我们一起来看看如何构建自己的镜像并上传到镜像仓库中。 &#x1f3e0;个人主页&#xff1a;我是沐风晓月 &#x1f9d1;个人简介&…

【Python】字符串 ⑤ ( Python 字符串快速格式化 | 不考虑变量类型 | 不考虑精度控制 )

文章目录一、Python 字符串快速格式化1、语法说明2、代码示例 - 不考虑变量类型3、代码示例 - 不考虑精度控制4、快速格式化的优点一、Python 字符串快速格式化 1、语法说明 Python 字符串快速格式化 : 通过如下格式的代码 , 可以进行字符串的快速格式化 ; f"字符串内容{…

vscode代码片段生成

在刚学习vue的时候&#xff0c;有些代码片段是经常写的&#xff0c;在vscode中写一个代码片段可以帮助快速生成。 生成步骤&#xff1a; VSCode中的代码片段有固定的格式&#xff0c;所以我们一般会借助于一个在线工具来完成。 具体的步骤如下: 第一步&#xff0c;复制自己需…

〖Python网络爬虫实战⑨〗- 正则表达式基本原理

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付…

Mac PicGo可以上传GitHub但是不能显示

Mac PicGo可以上传到GitHub但是本地不能显示&#xff08;已经加载的&#xff09;图片 背景&#xff1a;使用Typora PicGo GitHub 图床。 文章目录Mac PicGo可以上传到GitHub但是本地不能显示&#xff08;已经加载的&#xff09;图片1. Bug表现2. 解决方法&#xff08;1&…

【好书推荐】认知觉醒:开启自我改变的原动力

书籍信息 书名&#xff1a;认知觉醒&#xff1a;开启自我改变的原动力 作者&#xff1a; 周岭 出版社&#xff1a; 人民邮电出版社 认知觉醒的基础 重新认识大脑 在我们的大脑里&#xff0c;由内到外至少有三重大脑&#xff1a;年代久远的本能脑、相对古老的情绪脑和非常年…

【C语言深入】逐汇编详解函数栈帧的创建和销毁过程

【C语言深入】逐汇编详解函数栈帧的创建和销毁过程一、图解大概过程二、函数栈帧的创建过程1、简介一些需要用到的汇编指令和寄存器2、调用main函数的函数3、局部变量的初始化4、形成临时拷贝5、函数调用6、形成栈帧7、提取临时拷贝8、return返回三、函数栈帧的销毁过程1、释放…

python:异常处理与文件操作(知识点详解+代码展示)

文章目录一、异常处理1、try...except语句2、finally语句二、断言1、定义2、举例例一&#xff1a;例二&#xff1a;三、文件操作1、写文件操作2、读文件操作&#xff08;当你心情低落时候&#xff0c;记得外面还有美好的风景&#xff01;&#xff09; 学习目标&#xff1a; 1、…

堆相关的面试题

文章目录1. 距离不超过k的推排序2. 最大线段重合问题1. 距离不超过k的推排序 题目&#xff1a;已知一个几乎有序的数组。几乎有序是指&#xff0c;如果把数组排好顺序的话&#xff0c;每个元素移动的距离一定不超过k&#xff0c;并且k相对于数组长度来说是比较小的。 请选择一…

WinRAR压缩解压文件

使用WinRAR压缩管理器压缩解压文件详细步骤如下&#xff1a; ■ 压缩文件 ① 鼠标右键需要压缩的文件&#xff0c;点击“添加到压缩文件”&#xff0c;具体操作步骤如图所示&#xff1a; ② 压缩后的对应文件压缩包会显示在桌面&#xff0c;如图所示&#xff1a; ■ 解压文件 …

如何设计一个高并发系统

目录 如何理解高并发系统 1. 分而治之&#xff0c;横向扩展 2. 微服务拆分&#xff08;系统拆分&#xff09; 3. 分库分表 4. 池化技术 5. 主从分离 6. 使用缓存 7. CDN——加速静态资源访问 8. 消息队列——削锋 9. ElasticSearch 10. 降级熔断 11. 限流 12. 异步…

【OpenLayers】VUE+OpenLayers+ElementUI加载WMS地图服务

【OpenLayers】VUEOpenLayersElementUI加载WMS地图服务准备工作安装vue创建vue项目安装OpenLayers安装ElementUI加载wms地图服务准备工作 需要安装好nodejs&#xff0c;nodejs下载地址&#xff0c;下载对应的版本向导式安装即可。 安装完成后&#xff0c;控制台输入node -v&a…

【CentOS 7安装MySQL 8的教程指南】

CentOS 7安装MySQL 8 添加MySQL官方源 wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm sudo rpm -ivh mysql80-community-release-el7-3.noarch.rpm安装MySQL 8 sudo yum install mysql-community-server安装失败执行下面的命令并再次执行安装…

进程与线程的区别和联系

进程与线程的区别和联系&#x1f50e;进程&#x1f50e;线程&#x1f50e;进程与线程的联系&#x1f50e;进程与线程的区别&#x1f50e;总结&#x1f50e; 结尾&#x1f50e;进程 进程简单来说就是运行着的程序 如果不太理解可以参考一下这篇文章 进程 &#x1f50e;线程 …