数据结构:并查集

并查集的原理

在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个
单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一
个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为并查集(union-find
set)。

举个例子看看
比如:某公司今年校招全国总共招生10人,西安招4人,成都招3人,武汉招3人,10个人来自不
同的学校,起先互不相识,每个学生都是一个独立的小团体,现给这些学生进行编号:{0, 1, 2, 3,
4, 5, 6, 7, 8, 9}; 给以下数组用来存储该小集体,数组中的数字代表:该小集体中具有成员的个
数。

在这里插入图片描述

至于,为什么是负号呢?
聪明的小伙伴们,可以先自己思考一下(为什么人数要用负数表示呢)

毕业后,学生们要去公司上班,每个地方的学生自发组织成小分队一起上路,于是:
西安学生小分队s1={0,6,7,8},成都学生小分队s2={1,4,9},武汉学生小分队s3={2,3,5}就相互认识
了,10个人形成了三个小团体。假设右三个群主0,1,2担任队长,负责大家的出行。
于是就变成了三个小团体,用三棵树表示
在这里插入图片描述

然后,我们将这三棵树,也就是三个集合,用双亲表示法,写到一个数组中,这就是并查集。
在这里插入图片描述

所以,并查集其实就是一个森林

看到这里,相信,大家应该能够知道为什么会用负数表示人数

OK,
总结一下,

  1. 当数组中的元素大于0的时候,该元素就是该节点的父节点的下标
  2. 当数组中的元素小于0的时候,该元素的绝对值就是这颗树,也就是这个集合中的元素的个数

但是,还是存在一个问题,
其实注意看,我们的十名同学编号为0到9,这其实是一个映射,我们在实现的时候建议将这些映射用map存起来,当需要根据同学找编号的时候,就能够发挥作用了。

并查集的实现

并查集主要需要实现以下功能

  1. 合并
  2. 查找
  3. 返回集合的个数
  4. 判断两个元素是不是在同一个集合

并查集的结构

本质结构是一个森林,用vector来存
但我们最好还需要存一个map,来存映射关系(当然,也可以不存)

在这里插入图片描述

查找根节点的位置

查找根节点的位置是并查集操作的核心

如何查找根节点的位置
我们只需要根据下面的两条性质即可

  1. 当数组中的元素大于0的时候,该元素就是该节点的父节点的下标
  2. 当数组中的元素小于0的时候,该元素的绝对值就是这颗树,也就是这个集合中的元素的个数

一直循环向上找根节点即可,最后返回下标

int FindRoot(int a)
{while(_uft[a] >= 0){a = _uft[a];}return a;
}

集合的合并

集合的合并,我们只需要去处理根即可,比如下图中

在这里插入图片描述
如果我们需要合并4和7,我们只需要找到4所在的集合和7所在的集合,将两个集合合并就行
而合并的时候我们只需要处理根,将1合并到0的下面

具体怎么操作
很简单

我们只需要将1对应的下标中存的个数,加到0对应的下标中存的个数中,在将1对应的下标中存的元素改成0的下标即可。

void Union(int a,int b)
{int root1 = FindRoot(a);int root2 = FindRoot(b);if(root1 == roo2)//在同一个集合就不用合并return;_uft[root1] += _uft[root2];_uft[root2] = root1;}

并查集中集合的个数

太简单了,直接遍历vector,记录其中负元素的个数
返回即可。

int SetSize()
{int ret = 0;for(auto& e:_uft){if (e < 0)ret++;}return ret;
}

判断两个元素是不是在同一个集合

太简单了,直接找两个元素的根,然后进行比较即可

bool IsSameSet(int a, int b)
{return FindRoot(a) == FindRoot(b);
}

并查集的优化

并查集确实挺不错的,但是,可能在合并多次之后,导致其中有集合的高度过高了,这个时候,查找的时候就不那么好用了。

于是,这里就进行路径压缩,来缩短集合的高度。

网络上有一些地方写这里路径压缩的时候采用递归去写
但是呢,高度已经很高了,用递归的话,资源消耗有会继续加大,所以,我建议采用非递归去写。

主要在两个地方可以进行优化

合并的优化

在合并的时候,我们规定节点少的集合合并到节点多的集合
这样集合的高度就没有增加,这里有点抽象
我们这么想

当节点少的集合合并到节点多的集合
新的高度就是max(节点多的集合,节点少的集合 + 1)
高度没有发生变化

而当节点多的集合合并到节点少的集合
新的高度就是max(节点多的集合 + 1,节点少的集合)
高度增加了

显然,我们应该要将节点少的集合合并到节点多的集合
所以,优化后的合并代码如下

void Union(int a, int b)
{int root1 = FindRoot(a);int root2 = FindRoot(b);if (root1 == root2)//如果在同一个集合就不用合并return;if (abs(_uft[root1]) < abs(_uft[root2]))//把节点少的集合合并到节点大的集合{swap(root1, root2);}_uft[root1] += _uft[root2];_uft[root2] = root1;
}

查找根节点的优化

这里就是真正的路径压缩了
我们在查找根的时候,如果经历了多次才找到根,那么就可以将这个节点,直接加到根下面去,这样就可以缩短高度。

其实就是,我们在找到根之后,将路径上的节点全部都挂到根下面去,通过这样的操作来缩短高度

int FindRoot(int a)
{int root = a;;while (_uft[root] >= 0){root = _uft[root];}while (_uft[a] >= 0)//路径压缩{int parent = _uft[a];//记录上一个节点的下标_uft[a] = root;a = parent;}return root;
}

并查集的应用

我们在这里看两道题目

省份数量

链接:leetcode链接
在这里插入图片描述

思路分析
我们将一个省份作为一个集合,当两个城市相连,就合并
遍历完之后,再返回并查集中集合的个数即可。

template<class K>
class UnionFindSet
{
private:vector<int> _uft;//并查集map<K, int> _indexMap;//下标映射关系public:UnionFindSet(int n = 10){_uft.resize(n, -1);}void Union(int a, int b){int root1 = FindRoot(a);int root2 = FindRoot(b);if (root1 == root2)//如果在同一个集合就不用合并return;if (abs(_uft[root1]) < abs(_uft[root2]))//把节点少的集合合并到节点大的集合{swap(root1, root2);}_uft[root1] += _uft[root2];_uft[root2] = root1;}int FindRoot(int a){int root = a;;while (_uft[root] >= 0){root = _uft[root];}while (_uft[a] >= 0)//路径压缩{int parent = _uft[a];//记录上一个节点的下标_uft[a] = root;a = parent;}return root;}int SetSize(){int ret = 0;for (int i = 0; i < _uft.size(); ++i){if (_uft[i] < 0)ret++;}return ret;}bool IsSameSet(int a, int b){return FindRoot(a) == FindRoot(b);}
};class Solution {
public:int findCircleNum(vector<vector<int>>& isConnected) {UnionFindSet<int> uft(isConnected.size());for(int i = 0;i < isConnected.size();++i){for(int j = 0;j < isConnected[0].size();++j){if(isConnected[i][j]==1){uft.Union(i,j);}}}return uft.SetSize();}
};

等式方程式的可满足性

链接:leetcode链接

在这里插入图片描述

我们用26个字母去建立一个26个元素的并查集
我们遍历字符串数组,遇到等式的时候,就去合并等式两边的元素所在的集合
遍历完之后,就建立好了并查集
然后开始查找即可
接着再去遍历一遍字符串数组,遇到不等式的时候,去判断等式两边的元素是否不在同一个集合

template<class K>
class UnionFindSet
{
private:vector<int> _uft;//并查集map<K, int> _indexMap;//下标映射关系public:UnionFindSet(int n = 10){_uft.resize(n, -1);}void Union(int a, int b){int root1 = FindRoot(a);int root2 = FindRoot(b);if (root1 == root2)//如果在同一个集合就不用合并return;if (abs(_uft[root1]) < abs(_uft[root2]))//把节点少的集合合并到节点大的集合{swap(root1, root2);}_uft[root1] += _uft[root2];_uft[root2] = root1;}int FindRoot(int a){int root = a;;while (_uft[root] >= 0){root = _uft[root];}while (_uft[a] >= 0)//路径压缩{int parent = _uft[a];//记录上一个节点的下标_uft[a] = root;a = parent;}return root;}int SetSize(){int ret = 0;for (int i = 0; i < _uft.size(); ++i){if (_uft[i] < 0)ret++;}return ret;}bool IsSameSet(int a, int b){return FindRoot(a) == FindRoot(b);}
};class Solution {
public:bool equationsPossible(vector<string>& equations) {UnionFindSet<string> uft(26);for(auto& e:equations){if(e[1] == '='){uft.Union(e[0] - 'a',e[3] - 'a');}}for(auto& e:equations){if(e[1] == '!'){if(uft.IsSameSet(e[0] - 'a',e[3] - 'a') == true)return false;}}return true;}
};

在做算法提的时候简化并查集

并查集用起来爽啊,这么方便,但是,真的每次都要手搓一个并查集出来吗?

其实是不需要的,我们借助lambda表达式,可以实现出找根节点下标的函数即可,其他的函数,可以很快的在过程中写出来

我们以第二道题为例
简化一下代码

bool equationsPossible(vector<string>& equations) {vector<int> uft(26,-1);auto FindRoot = [&uft](int a){while(uft[a] >= 0)a = uft[a];return a;};for(auto& e:equations){if(e[1] == '='){int root1 = FindRoot(e[0] - 'a');int root2 = FindRoot(e[3] - 'a');if(root1 != root2){uft[root1] += uft[root2];uft[root2] = root1;}}}for(auto& e:equations){if(e[1] == '!'){int root1 = FindRoot(e[0] - 'a');int root2 = FindRoot(e[3] - 'a');if(root1 == root2)return false;}}return true;}

利用lambda表达式可以快速构建起来简化并查集,在做题的时候非常好用。


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

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

相关文章

ChatGPT新体验:AI搜索功能与订阅支付指南

就在凌晨&#xff0c;在ChatGPT迎来两周岁生日之际&#xff0c;OpenAI重磅发布了ChatGPT的全新人工智能搜索体验。 期待已久的时刻终于到来&#xff0c; ChatGPT正式转型成为一款革命性的AI搜索引擎! 先来看看ChatGPT搜索&#xff1a;这次不是简单的加个搜索框&#xff0c;而…

JS | 如何更好地优化 JavaScript 的内存回收?

目录 一、理解JavaScript内存生命周期 ● 创建对象和分配内存 ● 内存的使用 ● 内存回收 二、减少内存泄露 ● 避免全局变量 ● 正确使用闭包 三、合理管理内存 ● 局部变量和即时函数 ● 解绑事件监听器 四、使用现代JavaScript特性辅助内存回收 ● 使用WeakMap和…

群控系统服务端开发模式-应用开发-上传配置功能开发

下面直接进入上传配置功能开发&#xff0c;废话不多说。 一、创建表 1、语句 CREATE TABLE cluster_control.nc_param_upload (id int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 编号,upload_type tinyint(1) UNSIGNED NOT NULL COMMENT 上传类型 1&#xff1a;本站 2&a…

Cisco Packet Tracer 8.0 路由器的基本配置和Telnet设置

文章目录 构建拓扑图配置IP地址配置路由器命令说明测试效果 构建拓扑图 1&#xff0c;添加2811路由器。 2&#xff0c;添加pc0。 3&#xff0c;使用交叉线连接路由器和pc&#xff08;注意线路端口&#xff09;。 4&#xff0c;使用配置线连接路由器和pc&#xff08;注意线路…

从气象中心采集cma台风路径数据

在自然灾害监测与预警领域&#xff0c;台风作为一种极具破坏力的自然现象&#xff0c;其路径预测和强度评估对于减少潜在损失至关重要。随着互联网技术的发展&#xff0c;国家气象中心等专业机构提供了详尽的台风历史数据和实时跟踪服务&#xff0c;通过网络接口可便捷地访问这…

ssm+vue665基于Java的壁纸网站设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php phython node.js uniapp 微信小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不…

Applied Artificial Intelligence

文章目录 一、征稿简介二、重要信息三、服务简述四、投稿须知五、联系咨询 一、征稿简介 二、重要信息 期刊官网&#xff1a;https://ais.cn/u/3eEJNv 三、服务简述 四、投稿须知 1.在线投稿&#xff1a;由艾思科蓝支持在线投稿&#xff0c;请将文章全文投稿至艾思科蓝投稿…

oracle-函数-NULLIF (expr1, expr2)的妙用

【语法】NULLIF (expr1, expr2) 【功能】expr1和expr2相等返回NULL&#xff0c;不相等返回expr1经典的使用场景&#xff1a; 1. 数据清洗与转换 在数据清洗过程中&#xff0c;NULLIF 函数可以用于将某些特定值&#xff08;通常是无效或不需要的值&#xff09;替换为 NULL&…

pycharm 安装

双击pycharm-community-2024.2.0.1.exe安装包 可以保持默认&#xff0c;也可以改成D&#xff0c;如果你有D 盘 全选&#xff0c;下一步 安装完成 在桌面创建一个文件夹任意名字 拖动到pycharm 图标打开 如果出现这个勾选信任即可 下面准备汉化&#xff08;喜欢英语界面的…

Matlab实现蚁群算法求解旅行商优化问题(TSP)(理论+例子+程序)

一、蚁群算法 蚁群算法由意大利学者Dorigo M等根据自然界蚂蚁觅食行为提岀。蚂蚁觅食行为表示大量蚂蚁组成的群体构成一个信息正反馈机制&#xff0c;在同一时间内路径越短蚂蚁分泌的信息就越多&#xff0c;蚂蚁选择该路径的概率就更大。 蚁群算法的思想来源于自然界蚂蚁觅食&a…

计算机毕业设计Hadoop+大模型高考推荐系统 高考分数线预测 知识图谱 高考数据分析可视化 高考大数据 大数据毕业设计 Hadoop 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 开题报告 题目&#xff1a…

【qwen2-1.5-instruct 好于Gemma2-2b-instruct\Llama3.2-1B-instruct】

最新的qwen Llama Gemma小参数模型比较&#xff0c;移动端 qwen2-1.5-instruct 好于Gemma2-2b-instruct\Llama3.2-1B-instruct 从 Qwen2–1.5B-instruct 到 Gemma2–2B-instruct&#xff0c;再到 Llama3.2–1B-instruct&#xff0c;最后是新的 Qwen2.5–1.5B-instruct。虽然我…

C++之位算法

位算法 常见位运算总结 位1的个数 给定一个正整数 n&#xff0c;编写一个函数&#xff0c;获取一个正整数的二进制形式并返回其二进制表达式中 设置位 的个数&#xff08;也被称为汉明重量&#xff09;。 示例 1&#xff1a; 输入&#xff1a;n 11 输出&#xff1a;3 解释…

JAVA利用方法实现四道题

目录 1.给定一个字符串 s &#xff0c;找到 它的第一个不重复的字符&#xff0c;并返回它的索引 。如果不存在&#xff0c;则返回-1 2.计算字符串最后一个单词的长度&#xff0c;单词以空格隔开。&#xff08;注&#xff1a;字符串末尾不以空格为结尾&#xff09; 3.如果在将所…

【教程】Git 标准工作流

前言 Git 是日常开发中常用的版本控制工具&#xff0c;配合代码托管仓库&#xff08;如&#xff0c;Github&#xff0c;GitLab&#xff0c;Gitee 等&#xff09;用来实现多人多版本的协作开发。 但是 Git 的命令纷繁复杂&#xff0c;多如累卵&#xff0c;不可能也不需要全部搞…

基于AI深度学习的中医针灸实训室腹针穴位智能辅助定位系统开发

在中医针灸的传统治疗中&#xff0c;穴位取穴的精确度对于治疗效果至关重要。然而&#xff0c;传统的定位方法&#xff0c;如体表标志法、骨度折量法和指寸法&#xff0c;由于观察角度、个体差异&#xff08;如人体姿态和皮肤纹理&#xff09;以及环境因素的干扰&#xff0c;往…

金融标准体系

目录 基本原则 标准体系结构图 标准明细表 金融标准体系下载地址 基本原则 需求引领、顶层设计。 坚持目标导向、问题导向、结果 导向有机统一&#xff0c;构建支撑适用、体系完善、科学合理的金融 标准体系。 全面系统、重点突出。 以金融业运用有效、保护有力、 管理高…

.NET 8 Web API 中的身份验证和授权

本次介绍分为3篇文章&#xff1a; 1&#xff1a;.Net 8 Web API CRUD 操作.Net 8 Web API CRUD 操作-CSDN博客 2&#xff1a;在 .Net 8 API 中实现 Entity Framework 的 Code First 方法https://blog.csdn.net/hefeng_aspnet/article/details/143229912 3&#xff1a;.NET …

Spring Boot 与 Vue 共铸卓越采购管理新平台

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 &#x1f345;获取源码联系方式请查看文末&#x1f345; 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目…

字符串统计(Python)

接收键盘任意录入&#xff0c;分别统计大小写字母、数字及其它字符数量&#xff0c;打印输出。 (笔记模板由python脚本于2024年11月02日 08:23:31创建&#xff0c;本篇笔记适合熟悉python字符串并懂得基本编程技法的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xf…