其它高阶数据结构①_并查集(概念+代码+两道OJ)

目录

1. 并查集的概念

2. 并查集的实现

3. 并查集的应用

3.1 力扣LCR 116. 省份数量

解析代码1

解析代码2

3.2 力扣990. 等式方程的可满足性

解析代码

本篇完。


写在前面:

        此高阶数据结构系列,虽然放在⑤数据结构与算法专栏,但还是作为一个拓展学习,建议跳过第⑤序号跟着其它专栏序号学,当时是想着要期末考和考研的同学,考到图才开这个专栏的吧,其他不急的同学可以在学完MySQL专栏后再看,此系列也放在了⑩其它高阶数据结构专栏,这里简单学习并查集是为了下一个数据结构“图”的学习。


1. 并查集的概念

  • 并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。
  • 并查集通常用森林来表示,森林中的每棵树表示一个集合,树中的结点对应一个元素。

        虽然利用其它数据结构也能完成不相交集合的合并及查询,但在数据量极大的情况下,其耗费的时间和空间也是极大的。

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

        并查集是多个独立集合的合集,用于表示数据之间的关系,并查集中的每一个集合是用多叉树来表示的。

        比如:某公司今年校招全国总共招生10人,西安招4人,成都招3人,武汉招3人,10个人来自不 同的学校,起先互不相识,每个学生都是一个独立的小团体,现给这些学生进行编号:{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 给以下数组用来存储该小集体,数组中的数字代表:该小集体中具有成员的个 数。数组中某个位置的值为负数,表示该位置是树的根,这个负数的绝对值表示的这棵树(集合)中数据的个数,因为刚开始每个人各自属于一个集合,所以将数组中的位置都初始化为-1。

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

一趟火车之旅后,每个小分队成员就互相熟悉,称为了一个朋友圈。

        从上图可以看出:编号6,7,8同学属于0号小分队,该小分队中有4人(包含队长0);编号为4和9的同 学属于1号小分队,该小分队有3人(包含队长1),编号为3和5的同学属于2号小分队,该小分队有3 个人(包含队长1)。 仔细观察数组中内变化,可以得出以下结论:

  1. 数组的下标对应集合中元素的编号。
  2. 数组中如果为负数,负号代表根,数字代表该集合中元素个数。
  3. 数组中如果为非负数,代表该元素双亲在数组中的下标。

        在公司工作一段时间后,西安小分队中8号同学与成都小分队1号同学奇迹般的走到了一起,两个小圈子的学生相互介绍,最后成为了一个小圈子:

        现在0集合有7个人,2集合有3个人,总共两个朋友圈。通过以上例子可知,并查集一般可以解决一下问题:

  1. 查找元素属于哪个集合:沿着数组表示树形关系以上一直找到根(即:树中中元素为负数的位置)。
  2. 查看两个元素是否属于同一个集合:沿着数组表示的树形关系往上一直找到树的根,如果根相同表明在同一个集合,否则不在。
  3. 将两个集合归并成一个集合:将两个集合中的元素合并,将一个集合名称改成另一个集合的名称。
  4. 集合的个数:遍历数组,数组中元素为负数的个数即为集合的个数。

2. 并查集的实现

代码实现还是很简单的,直接放出代码:(建议复制到自己编译器跟着注释一起看)

#pragma once#include <iostream>
#include <vector>
using namespace std;class UnionFindSet
{
private:vector<int> _ufs;public:UnionFindSet(size_t size) // 初始时,将数组中元素全部设置为1: _ufs(size, -1){}int FindRoot(int index) // 给一个元素的编号,找到该元素所在集合的名称{int root = index;while (_ufs[root] >= 0)   // 如果数组中存储的是负数,找到,否则一直继续{root = _ufs[root];}while (_ufs[index] >= 0) // 路径压缩{int parent = _ufs[index];_ufs[index] = root;index = parent;}return index;}bool InSet(int x1, int x2){return FindRoot(x1) == FindRoot(x2);}bool Union(int x1, int x2) // 合并两个集合{int root1 = FindRoot(x1);int root2 = FindRoot(x2);if (root1 == root2) // x1已经与x2在同一个集合return false;if (abs(_ufs[root1]) < abs(_ufs[root2])) // 控制数据量小的往大的集合合并swap(root1, root2);_ufs[root1] += _ufs[root2]; // 负号代表根,数字代表该集合中元素个数_ufs[root2] = root1; // 将其中一个集合名称改变成另外一个return true;}size_t Count() const  // 数组中负数的个数,即为集合的个数{size_t count = 0;for (auto e : _ufs){if (e < 0)++count;}return count;}
};void TestUFS()
{UnionFindSet u(10);u.Union(0, 6);u.Union(7, 6);u.Union(7, 8);u.Union(1, 4);u.Union(4, 9);u.Union(2, 3);u.Union(2, 5);u.FindRoot(6);u.FindRoot(9);cout << u.Count() << endl;
}


3. 并查集的应用

直接复制上面并查集的代码到力扣写两道题:

3.1 力扣LCR 116. 省份数量

LCR 116. 省份数量

难度 中等

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

示例 1:

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2

示例 2:

输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

提示:

  • 1 <= n <= 200
  • n == isConnected.length
  • n == isConnected[i].length
  • isConnected[i][j] 为 1 或 0
  • isConnected[i][i] == 1
  • isConnected[i][j] == isConnected[j][i]

注意:本题与主站 547 题相同: 547. 省份数量

class Solution {
public:int findCircleNum(vector<vector<int>>& isConnected) {}
};

解析代码1

直接复制并查集过来:

class UnionFindSet
{
private:vector<int> _ufs;public:UnionFindSet(size_t size) // 初始时,将数组中元素全部设置为1: _ufs(size, -1){}int FindRoot(int index) // 给一个元素的编号,找到该元素所在集合的名称{int root = index;while (_ufs[root] >= 0)   // 如果数组中存储的是负数,找到,否则一直继续{root = _ufs[root];}while (_ufs[index] >= 0) // 路径压缩{int parent = _ufs[index];_ufs[index] = root;index = parent;}return index;}bool InSet(int x1, int x2){return FindRoot(x1) == FindRoot(x2);}bool Union(int x1, int x2) // 合并两个集合{int root1 = FindRoot(x1);int root2 = FindRoot(x2);if (root1 == root2) // x1已经与x2在同一个集合return false;if (abs(_ufs[root1]) < abs(_ufs[root2])) // 控制数据量小的往大的集合合并swap(root1, root2);_ufs[root1] += _ufs[root2]; // 负号代表根,数字代表该集合中元素个数_ufs[root2] = root1; // 将其中一个集合名称改变成另外一个return true;}size_t Count() const  // 数组中负数的个数,即为集合的个数{size_t count = 0;for (auto e : _ufs){if (e < 0)++count;}return count;}
};class Solution {
public:int findCircleNum(vector<vector<int>>& isConnected) {UnionFindSet ufs(isConnected.size());for (size_t i = 0; i < isConnected.size(); ++i){for (size_t j = 0; j < isConnected[i].size(); ++j){if (isConnected[i][j] == 1) // 合并集合{ufs.Union(i, j);}}}return ufs.Count();}
};


解析代码2

用数组模拟并查集:

class Solution {
public:int findCircleNum(vector<vector<int>>& isConnected) {vector<int> ufs(isConnected.size(), -1); // 手动控制并查集auto findRoot = [&ufs](int x) // 查找根{while(ufs[x] >= 0)x = ufs[x];return x;};for(size_t i = 0; i < isConnected.size(); ++i){for(size_t j = 0; j < isConnected[i].size(); ++j){if(isConnected[i][j] == 1) // 合并集合{int root1 = findRoot(i);int root2 = findRoot(j);if (root1 != root2){ufs[root1] += ufs[root2];ufs[root2] = root1;}}}}int cnt = 0;for(auto e : ufs){if(e < 0)++cnt;}return cnt;}
};


3.2 力扣990. 等式方程的可满足性

990. 等式方程的可满足性

难度 中等

给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:"a==b" 或 "a!=b"。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。

只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false。 

示例 1:

输入:["a==b","b!=a"]
输出:false
解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量同时满足这两个方程。

示例 2:

输入:["b==a","a==b"]
输出:true
解释:我们可以指定 a = 1 且 b = 1 以满足满足这两个方程。

示例 3:

输入:["a==b","b==c","a==c"]
输出:true

示例 4:

输入:["a==b","b!=c","c==a"]
输出:false

示例 5:

输入:["c==c","b==d","x!=z"]
输出:true

提示:

  1. 1 <= equations.length <= 500
  2. equations[i].length == 4
  3. equations[i][0] 和 equations[i][3] 是小写字母
  4. equations[i][1] 要么是 '=',要么是 '!'
  5. equations[i][2] 是 '='
class Solution {
public:bool equationsPossible(vector<string>& equations) {}
};

解析代码

并查集的变形,思路:

  1. 将所有"=="两端的字符合并到一个集合中。
  2. 检测"!=" 两端的字符是否在同一个集合中,如果在,不满足,如果不在,满足。
class Solution {
public:bool equationsPossible(vector<string>& equations) {vector<int> ufs(26, -1);auto findRoot = [&ufs](int x){while(ufs[x] >= 0)x = ufs[x];return x;};for(auto& e : equations) // 第一遍,先把相等的值加到一个集合中{if(e[1] == '='){int root1 = findRoot(e[0] - 'a');int root2 = findRoot(e[3] - 'a');if(root1 != root2){ufs[root1] += ufs[root2];ufs[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;}
};


本篇完。

这里简单学习并查集更多是为了下一个数据结构“图”的学习,一些竞赛的OJ也会用到并查集。

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

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

相关文章

安全风险 - 如何解决 setAccessible(true) 带来的安全风险?

可能每款成熟的金融app上架前都会经过层层安全检测才能执行上架&#xff0c;所以我隔三差五就能看到安全检测报告中提到的问题&#xff0c;根据问题的不同级别&#xff0c;处理的优先级也有所不同&#xff0c;此次讲的主要是一个 “轻度问题” &#xff0c;个人认为属于那种可改…

ui之资源

主题 样式 菜单 上下文菜单 国际化 1字符串资源 xml java代码中使用 //res-valuse-strings.xml 默认创建的 getResources().getString(R.string.x)2 实例 3 颜色资源语法 alpha red green blue <color 行 色块 择色器 定义文件&#xff0c;用 半透明 #440000FF 常用&…

Python 函数式编程

匿名函数 Python 允许用 lambda 关键字创造匿名函数。匿名顾名思义就是没有名字&#xff0c;即不需要以标准的方式来声明&#xff0c;比如说&#xff0c;使用 def 加函数名来声明。一个完整的 lambda “语句”代表了一个表达式&#xff0c;这个表达式的定义体必须和声明放在同…

2024CKE中国婴童展

举办地点&#xff1a;上海新国际博览中心 举办时间&#xff1a;2024年10月16-18日 同期展会&#xff1a;CTE中国玩具展、CPE中国幼教展、CLE中国授权展 展会规模&#xff1a;230,000平米 展商数量&#xff1a;2,500 参展品牌&#xff1a;5,212 …

【数据库】数据库指令

一。数据库打开 1.命令行 2.进入mysql mysql -uroot -p密码 3.退出 exit&#xff1b; 二。针对数据库的操作 1.创建数据库&#xff08;有分号&#xff09; create database student; 2.使用数据库 use student 3.删除数据库&#xff08;有分号&#xff09; drop database…

神经网络复习--卷积神经网络及其扩展

文章目录 卷积卷积网络反向传播已知池化层误差&#xff0c;反向求上一层隐藏层误差已知卷积层的误差&#xff0c;推导该层的W, b的梯度 注意力机制Transformer卷积变体 卷积 卷积神经网络是一个多层的神经网络&#xff0c;每层由多个二维平面组成&#xff0c;每个平面由多个独…

Llama 3 超级课堂 -笔记

课程文档&#xff1a; https://github.com/SmartFlowAI/Llama3-Tutorial 课程视频&#xff1a;https://space.bilibili.com/3546636263360696/channel/series 1 环境配置 1.1 创建虚拟环境,名为&#xff1a;llama3 conda create -n llama3 python3.10 1.2 下载、安装 pyt…

Prosys OPC UA Simulation Server工程文件备份方法

Prosys OPC UA Simulation Server是一款免费的OPC UA服务器仿真软件&#xff0c;具体的使用和下载参考官网&#xff1a; Prosys OPC - OPC UA Simulation Server Downloads 他的免费版本不提供工程文件的备份、导入导出功能&#xff0c;每次退出时保存。如果需要工程备份&a…

Java框架精品项目【用于个人学习】

源码获取&#xff1a;私聊回复【项目关键字】获取 更多选题参考&#xff1a; Java练手项目 & 个人学习等选题参考 推荐菜鸟教程Java学习、Javatpoint学习 前言 大家好&#xff0c;我是二哈喇子&#xff0c;此博文整理了各种项目需求 此文下的项目用于博主自己学习&#x…

vue2人力资源项目8员工详情

页面结构 <template><div class"dashboard-container"><div class"app-container"><div class"edit-form"><el-form ref"userForm" label-width"220px"><!-- 姓名 部门 --><el-row…

树莓派发送指令控制FPGA板子上的流水灯程序

文章目录 前言一、树莓派简介二、整体实现步骤三、树莓派设置四、树莓派串口代码五、Verilog代码5.1 串口接收模块5.2 流水灯模块 六、quartus引脚绑定七、 运行效果总结参考 前言 ​ 本次实验的目的是通过树莓派和FPGA之间的串口通信&#xff0c;控制FPGA开发板上的小灯。实验…

《无畏契约》游戏画面出现“撕裂感“,你清楚背后的原理吗?

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (91平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;Java …