并查集的原理及其优化

news/2025/3/29 0:30:48/文章来源:https://www.cnblogs.com/shubei/p/18790102

并查集

顾名思义,并查集是支持“合并”和“查询”操作的集合。

合并操作针对的是两个区域,是将在图论中称为非连通块的区域合并为一个连通块。

查询操作则是查找两个节点是否连通。

而对于无序性的集合来说,需要特别说明,并查集能够操作的是无向图,并不适用于有向图强连通性。

原理和结构

为了方便表示,我们用树这种数据结构来实现并查集,每个节点都有一个父节点,我们定义一个pre[i]数组用于存放第i个节点的父节点。初始化如下:

for(int i = 1;i<=n;i++) pre[i]=i;

这样我们就得到了n个指向自己的节点。

合并:

并查集是怎么实现合并操作的呢?最简单地,我们构造一条新的边,将被操作的两个节点连在一起,就能够实现合并操作。但是并不能直接合并,因为每个节点只有一个父节点。有这样一种情况:

节点1——节点2,此时我们认为节点1的父节点为节点2,也就是pre[1]=2,如果需要再将节点1和节点3合并。按照错误的思路,我们会写到pre[1]=3这样一个代码。注意到,此时节点1与节点2便不在连通,因为节点1指向了节点3。那么有人说,将节点2的父节点指向节点3不就好了。是的,这样问题就解决了,但是问题解决的原因是什么。节点1和节点2有什么不同?他们的关系是怎样的?

节点2是节点1的根。没错,之所以用节点2操作可以实现,就是因为节点2是根的这一特性。同样,我们也可以认为节点3是根,在这里将节点3指向节点2也是正确的。所以合并的关键就是对根进行操作。实现代码如下:

void merge(int x,int y){x=root(x),y=root(y);//先找到根节点if(x==y) return;//如果根节点相同,则已连通,直接returnpre[x]=y;//pre[y]=x也可以
}int root(int x){//方法一:递归return pre[x]==x?x:root(x);
}
int root(int x){//方法二:循环while(pre[x]!=x) x=pre[x];
}

查询:

我们已经知道了并查集的原理和结构,也实现了合并操作,那么查询也就不难了。如何查询两个节点是否连通?考虑到我们树的数据结构,越往根节点走,节点之间的关系应该是越密切,因此我们判断是否的连通的条件就是根节点是否相同,同根即连通。实现代码如下:

if(root(x)==root(y)) return true;
return false;

算法优化

上面是基础的模板,这里进一步思考有没有更加高效的算法。我们注意到,无论是合并还是查询的操作,关键都是在找根节点来进行操作,操作的过程并不复杂,往往就是一行代码,而主要的时间都花在了找根节点上,那么如何减少浪费在找根节点的时间呢?一方面,我们可以优化我们的具体找根的过程,比如通过记忆化,如果节点1的根节点是10,那么在1-10之间的所有节0点,其根节点也都应该是10,那么我们就可以采用路径压缩。另一方面,我们可以考虑合并时花点心思,将树树形结构合并的又矮又胖,也能减少查询的次数。

路径压缩

时间复杂度:O(1)

原理刚刚已经讲过了,就是那么回事,直接上代码:

int root(int x){//方法一:递归return pre[x]=(pre[x]==x?x:root(x));//找到根后返回的过程中,给路径上的节点的pre都赋上根节点的编号
}
int root(int x){//方法二:循环int res = x;while(pre[res]!=res) res=pre[res];//res为根 while(pre[x]!=x){int y = x;x=pre[x];pre[y]=res;}//类似于链表的插入操作,只不过这里是将链表中要指向的新的节点改成根节点
}

路径压缩有一个弊端:那就是改变了原来的树形结构

按秩合并

时间复杂度:O(logn)

秩可以理解为树高,我们尽量保证得到一个每个树支上的秩都比较平均的树,而非一个很深很深,每次找根都要从头找到尾的树,因此合并时,可以按秩进行合并。将秩小的节点合并到秩大的节点上,此时秩不会改变,而当两部分秩相同,则根节点的秩+1

int rnk[N];
void merge(int x,int y){x=root(X),y=root(y);if(x==y)return;if(rnk[x]>rnk[y]) swap(x,y)//保证rnk[x]<=rnk[y]pre[x]=y;//秩小的指向秩大的if(rnk[x]==rnk[y]) rnk[y]++;//相等时需要更新根节点的秩
}

启发式合并(按大小合并)

时间复杂度:O(logn)

大小和秩的本质类似,都是为了避免又长又深的树,代码也类似

int sz[N];
void merge(int x,int y){x=root(X),y=root(y);if(x==y)return;if(sz[x]>sz[y]) swap(x,y)//保证sz[x]<=sz[y]pre[x]=y;//小的指向大的sz[y]+=sz[x]//根节点更新大小
}int main(){
for(int i = 1;i<=n;i++)sz[i]=1;//不要忘记初始化,否则一直为0
}

总结大概就到此为止啦~

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

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

相关文章

事务注解@Transactional

目录 1、属性介绍 2、传播机制准备例子总结3、原理 4、失效场景 一、属性介绍 1、isolation 属性 事务的隔离级别,默认值为 Isolation.DEFAULT。可选的值有:Isolation.DEFAULT:使用底层数据库默认的隔离级别Isolation.READ_UNCOMMITTED:读取未提交数据(会出现脏读,不可重…

Nature | 整合空间转录组与GWAS数据绘制人复杂性状细胞空间图谱

这篇文章是2025年3月发表在《Nature》杂志上的一篇研究,标题为“Spatially resolved mapping of cells associated with human complex traits”。这篇文章通过开发一种名为gsMap的计算方法,整合高分辨率空间转录组(ST)数据与全基因组关联研究(GWAS)统计结果,实现对人类…

【笔记】力扣 102. 二叉树的层序遍历★

102. 二叉树的层序遍历 中等 提示 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 示例 1:输入:root = [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]]示例 2: 输入:root = [1] 输出:[[1]]示例 3: 输入:root = [] 输…

【Azure Container App】在消耗性的Container App Environmnet中无法查看当时正在使用多少CPU多少实例数的替代方案

问题描述 在Azure上使用Container App服务,创建服务环境的时候,选择类型为消耗型(Consumption)。但是,却无法查看当前所有应用中,具体使用了多少个 core (CPU), 目前有多少个实例呢? 如上图,上面的信息是 “-” 问题解答 因为在Container App Environment中所使用的Workl…

Redis短信登录场景

1、Redis短信登录场景 1.1、整体流程1.1、发送短信验证码 @Service @Slf4j public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic Result sendCode(String phone, HttpSession session) {// 1、校验手机号是否…

威联通TS-466C

威联通TS-466C CPU: Intel奔腾 银牌N6005 四核/ 四线程处理器,可高达 3.3 GHz 内存: 8G 3200MHz (可扩容到16G) 最大硬盘容量: 96T(22TB HDD x 4 + 4TB M.2 x 2)官网硬件参数: https://www.qnap.com.cn/zh-cn/product/ts-466c/specs/hardware 开箱安装硬盘 M2固态安装机械硬盘…

SvelteKit 最新中文文档教程(9)—— 部署静态站点与单页应用

前言 Svelte,一个语法简洁、入门容易,面向未来的前端框架。 从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1:Svelte 以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非常适合构…

FeedbackStream:8 分钟创建 AI 面试智能体;Moshi 开源图像理解实时语音模型 MoshiVis 丨日报

开发者朋友们大家好:这里是 「RTE 开发者日报」 ,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE(Real-Time Engagement) 领域内「有话题的 技术 」、「有亮点的 产品 」、「有思考的 文章 」、「有态度的 观点 」、「有看点的 活动 」,但内容仅代表编辑…

20234120 2024-2025-2 《Python程序设计》实验一报告

20234120 2024-2025-2 《Python程序设计》实验一报告 课程:《Python程序设计》 班级: 2341 姓名: 李卓颖 学号:20234120 实验教师:王志强老师 实验日期:2025年3月22日 必修/选修:专业选修课 一、实验内容 (一)准备工作:1.安装Python和PyCharm:访问Python和PyCharm官…

day:28 postman——断言

一.postman 断言 1.断言再test中 状态码是否等于200: tests["Status code is 200"] = responseCode.code === 200; 断言响应时间小于200ms: tests["Response time is less than 200ms"] = responseTime < 200; 断言响应体包含内容: tests["Bod…

XDA论坛打不开的解决办法

开了梯子反而进不去,关掉全局代理 v2 可以添加两行 https://xdaforums.com/; https://www.xda-developers.com/;