DS并查集(17)

文章目录

  • 前言
  • 一、何为并查集?
  • 二、并查集的实现?
    • 并查集的初始化
    • 查找元素所在的集合
    • 判断两个元素是否在同一个集合
    • 合并两个元素所在的集合
    • 获取并查集中集合的个数
    • 并查集的路径压缩
  • 三、来两道题练练手?
    • 省份的数量
    • 等式方程的可满足性
  • 总结


前言

  其实我一开始是想直接讲图的,但是但是考虑的图的 Kruskal 算法要用到,就先讲解下并查集吧!


一、何为并查集?

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

这可能太抽象了,我们举个具体的例子:

  以朋友圈为例,现在有10个人(从0开始编号),刚开始这10个人互不认识,所以各自属于一个集合
在这里插入图片描述
  并查集会用一个数组来表示这10个人之间的关系,数组的下标对应就是这10个人的编号,刚开始时数组中的元素都初始化为-1
在这里插入图片描述

数组中某个位置的值为负数,表示该位置是树的根,这个负数的绝对值表示的这棵树(集合)中数据的个数,因为刚开始每个人各自属于一个集合,所以将数组中的位置都初始化为-1

  后来这10个人之间通过相互认识,最终形成了三个朋友圈
在这里插入图片描述
  此时并查集数组中各个位置的值如下
在这里插入图片描述

数组中某个位置的值为非负数,表示该位置不是树的根,这个非负数的值就是这个结点的父结点的编号

  后来4号和8号又通过某种机遇互相认识了,这时他们所在的两个集合就需要进行合并,最终就变成了两个朋友圈

在这里插入图片描述
  需要注意的是,在根据两个元素合并两个集合时,需要先分别找到这两个元素所在集合的根结点,然后再将一个集合合并到另一个集合,并且合并后需要更新数组中根结点的值

在这里插入图片描述

为什么要找根节点?

  1. 如果这两个元素所在集合的根结点相同,说明这两个元素本身就在同一个集合,无需合并
  2. 合并集合后需要更新这两个集合的根结点的值

  而要判断两个元素是否在同一个集合,也就是判断这两个元素所在集合的根结点是否相同

二、并查集的实现?

  首先我们要想元素的下标是否对应,如果无法对应的话,我们一般利用容器 map 来存储元素的下标映射

template<class T>
class UnionFindSet
{
public:UnionFindSet(const T* a, size_t n){for (size_t i = 0 ; i < n ; i++) {_a.push_back(a[i]);_indexMap[a[i]] = i;}}
private:vector<T> _a; // 编号找人map<T, int> _indexMap;// 人找编号
};

  不过在这里,我们假设给的就是下标,也就是说私有成员就只有一个 vector< int > _ufs

并查集的初始化

  并查集中会用一个数组来维护各个结点之间的关系,在初始化并查集时,根据元素的个数开辟数组空间,并将数组中的元素初始化为 -1 即可

//构造函数
UnionFindSet(int n):_ufs(n, -1) //初始时各个元素自成一个集合
{}

查找元素所在的集合

查找逻辑如下:

  • 如果元素对应下标位置存储的是负数,则说明该元素即为根结点,返回该元素即可。
  • 如果元素对应下标位置存储的是非负数,则跳转到其父结点的位置继续查找根结点。
    int FindRoot(int x){int root = x;while (_ufs[root] >= 0){root = _ufs[root];}return root;}

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

    bool isInset(int x1, int x2){return FindRoot(x1) == FindRoot(x2);}

合并两个元素所在的集合

合并逻辑如下:

  • 分别找到两个元素所在集合的根结点。
  • 如果这两个元素所在集合的根结点相同,则无需合并,如果这两个元素所在集合的根结点不同,则将小集合合并到大集合上。
  • 将小集合根结点的值累加到大集合的根结点上,使得大集合根结点的值的绝对值等于两个集合中元素的总数。
  • 将小集合根结点的值改为大集合根结点的编号,也就是让小集合的根结点作为大集合根结点的孩子,使得两个集合变为一个集合。
    // 合并两个集合void Union(int x1, int x2){int root1 = FindRoot(x1);int root2 = FindRoot(x2);// 如果本身就在一个集合,那么无需合并if (root1 == root2) return ;// 合并的时候,作为孩子,其深度会增加一层// 因此,理论上来说,数据量小的作孩子// 如果有需求下标小的作根,则交换一下root// if (root1 > root2) swap(root1, root2); // 按照下标大小if (abs(_ufs[root1]) < abs(_ufs[root2])) swap(root1, root2); // 按照数据量大小// 如果不在同一个集合,则默认认为把x2代表集合合并到x1上,即x1作根,x2作子_ufs[root1] += _ufs[root2];// 将x2代表集合的名称改为x1_ufs[root2] = root1;}

获取并查集中集合的个数

  要获取并查集中集合的个数,本质就是统计数组中负值(根结点)的个数

    size_t SetSize(){size_t size = 0;for (size_t i = 0 ; i < _ufs.size() ; i++){if (_ufs[i] < 0) size++;}return size;}

并查集的路径压缩

  当数据量很大的时候,并查集中树的层数可能会变得很高,这时再查找一个元素所在集合的根结点时就需要往上走很多层,这时可以考虑进行路径压缩

  路径压缩一般会在查找根结点时进行,当根据一个结点查找其根结点时,该路径上所有的结点都会被压缩,最终这些结点会直接被挂在根结点下,下次再根据这些结点查找根结点时就能快速找到根结点

    int FindRoot(int x){int root = x;while (_ufs[root] >= 0){root = _ufs[root];}// 路径压缩,用于应对深度太深的情况// 把路径上所有节点都作为根的孩子while (_ufs[x] >= 0){int parent = _ufs[x];_ufs[x] = root;x = parent;}return root;}

三、来两道题练练手?

省份的数量

LCR 116. 省份数量

在这里插入图片描述
思路其实是很明确的:

  1. 定义一个长度为 n 的数组充当并查集,并将数组中的元素初始化为 -1,表示各个城市各自是一个省份。
  2. 根据所给矩阵,对并查集中的各个集合进行合并。
  3. 并查集中集合的个数即为省份的数量
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]){ufs.Union(i, j);}}}return ufs.SetSize();}};

等式方程的可满足性

990. 等式方程的可满足性

在这里插入图片描述
思路其实也是很明确的:

  1. 定义一个长度为26(变量为小写字母)的数组充当并查集,并将数组中的元素初始化为-1,表示各个字母只有自己等于自己。
  2. 根据字符串方程组中的等式,对并查集中的各个集合进行合并(每个集合中的元素都是相等的)。
  3. 根据并查集,对字符串方程组中的不等式进行验证,如果两个不相等的变量出现在同一个集合中,则返回 false 。
class Solution
{
public:bool equationsPossible(vector<string>& equations){UnionFindSet ufs(26);for (const auto& str : equations){if (str[1] == '='){ufs.Union(str[0] - 'a', str[3] - 'a');}}for (const auto& str : equations){if (str[1] == '!'){int root1 = ufs.FindRoot(str[0] - 'a');int root2 = ufs.FindRoot(str[3] - 'a');if (root1 == root2) return false;}}return true;}
};

总结

  其实,从这里开始的数据结构就比较困难了
  图、跳表、B树,它们要来了!!!

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

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

相关文章

Appium介绍

在使用不同版本的Appium包进行自动化测试时&#xff0c;出现警告问题可能是由于版本不兼容、配置不正确等原因导致的。下面将详细介绍解决这些问题的步骤&#xff0c;确保模拟器能够正常启动&#xff0c;并能在Appium查看器中同步显示。 1. 环境准备 首先&#xff0c;确保你已…

minimind - 从零开始训练小型语言模型

大语言模型&#xff08;LLM&#xff09;领域&#xff0c;如 GPT、LLaMA、GLM 等&#xff0c;虽然它们效果惊艳&#xff0c; 但动辄10 Bilion庞大的模型参数个人设备显存远不够训练&#xff0c;甚至推理困难。 几乎所有人都不会只满足于用Lora等方案fine-tuing大模型学会一些新的…

【C++动态规划 离散化】1626. 无矛盾的最佳球队|2027

本文涉及知识点 C动态规划 离散化 LeetCode1626. 无矛盾的最佳球队 假设你是球队的经理。对于即将到来的锦标赛&#xff0c;你想组合一支总体得分最高的球队。球队的得分是球队中所有球员的分数 总和 。 然而&#xff0c;球队中的矛盾会限制球员的发挥&#xff0c;所以必须选…

CSS 值和单位详解:从基础到实战

CSS 值和单位详解&#xff1a;从基础到实战 1. 什么是 CSS 的值&#xff1f;示例代码&#xff1a;使用颜色关键字和 RGB 函数 2. 数字、长度和百分比2.1 长度单位绝对长度单位相对长度单位 2.2 百分比 3. 颜色3.1 颜色关键字3.2 十六进制 RGB 值3.3 RGB 和 RGBA 值3.4 HSL 和 H…

Privacy Eraser,电脑隐私的终极清除者

Privacy Eraser 是一款专为保护用户隐私而设计的全能型软件&#xff0c;它不仅能够深度清理计算机中的各类隐私数据&#xff0c;还提供了多种系统优化工具&#xff0c;帮助用户提升设备的整体性能。通过这款软件&#xff0c;用户可以轻松清除浏览器历史记录、缓存文件、Cookie、…

Android 启动流程

一 Bootloader 阶段 在嵌入式系统中&#xff0c;Bootloader的引导过程与传统的PC环境有所不同&#xff0c;主要是因为嵌入式系统的硬件配置和应用场景更加多样化。以下是嵌入式系统中Bootloader被引导的一般流程&#xff1a; 1. 硬件复位 当嵌入式设备上电或复位时&#xff…

【数据结构与算法】AVL树的插入与删除实现详解

文章目录 前言Ⅰ. AVL树的定义Ⅱ. AVL树节点的定义Ⅲ. AVL树的插入Insert一、节点的插入二、插入的旋转① 新节点插入较高左子树的左侧&#xff08;左左&#xff09;&#xff1a;右单旋② 新节点插入较高右子树的右侧&#xff08;右右&#xff09;&#xff1a;左单旋③ 新节点插…

SCRM开发为企业提供全面客户管理解决方案与创新实践分享

内容概要 在当今的商业环境中&#xff0c;客户关系管理&#xff08;CRM&#xff09;变得越来越重要。而SCRM&#xff08;社交客户关系管理&#xff09;作为一种新兴的解决方案&#xff0c;正在帮助企业彻底改变与客户的互动方式。快鲸SCRM是一个引人注目的工具&#xff0c;它通…

AI应用部署——streamlit

如何把项目部署到一个具有公网ip地址的服务器上&#xff0c;让他人看到&#xff1f; 可以利用 streamlit 的社区云免费部署 1、生成requirements.txt文件 终端输入pip freeze > requirements.txt即可 requirements.txt里既包括自己安装过的库&#xff0c;也包括这些库的…

【C/C++】区分0、NULL和nullptr

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:C: 探索C编程精髓&#xff0c;打造高效代码仓库 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 1. 0 和空指针 2. NULL 3. nullptr 总结 …

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.1 NumPy高级索引:布尔型与花式索引的底层原理

2.1 NumPy高级索引&#xff1a;布尔型与花式索引的底层原理 目录 #mermaid-svg-NpcC75NxxU2mkB3V {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-NpcC75NxxU2mkB3V .error-icon{fill:#552222;}#mermaid-svg-NpcC75…

云原生(五十二) | DataGrip软件使用

文章目录 DataGrip软件使用 一、DataGrip基本使用 二、软件界面介绍 三、附件文件夹到项目中 四、DataGrip设置 五、SQL执行快捷键 DataGrip软件使用 一、DataGrip基本使用 1. 软件界面介绍 2. 附加文件夹到项目中【重要】 3. DataGrip配置 快捷键使用&#xff1a;C…

【Elasticsearch】match_bool_prefix 查询 vs match_phrase_prefix 查询

Match Bool Prefix Query vs. Match Phrase Prefix Query 在 Elasticsearch 中&#xff0c;match_bool_prefix 查询和 match_phrase_prefix 查询虽然都支持前缀匹配&#xff0c;但它们的行为和用途有所不同。以下是它们之间的主要区别&#xff1a; 1. match_bool_prefix 查询…

算法基础——存储

引入 基础理论的进步&#xff0c;是推动技术实现重大突破&#xff0c;促使相关领域的技术达成跨越式发展的核心。 在发展日新月异的大数据领域&#xff0c;基础理论的核心无疑是算法。不管是技术设计&#xff0c;还是工程实践&#xff0c;都必须仰仗相关算法的支持&#xff0…

正则表达式入门

入门 1、提取文章中所有的英文单词 //1&#xff0e;先创建一个Pattern对象&#xff0c;模式对象&#xff0c;可以理解成就是一个正则表达式对象 Pattern pattern Pattern.compile("[a-zA-Z]"); //2&#xff0e;创建一个匹配器对象 //理解:就是 matcher匹配器按照p…

分布式架构中的事务管理:需要了解的常见解决方案

前言 在现代互联网应用中&#xff0c;分布式架构越来越常见。随着系统规模的扩大&#xff0c;越来越多的业务和数据被分布到不同的服务和数据库中。虽然分布式架构带来了诸多优势&#xff0c;但也引入了一个新的问题&#xff1a;分布式事务。 一、什么是分布式事务&#xff1…

《TCP 网络编程实战:开发流程、缓冲区原理、三次握手与四次挥手》

一、 TCP 网络应用程序开发流程 学习目标 能够知道TCP客户端程序的开发流程1. TCP 网络应用程序开发流程的介绍 TCP 网络应用程序开发分为: TCP 客户端程序开发TCP 服务端程序开发说明: 客户端程序是指运行在用户设备上的程序 服务端程序是指运行在服务器设备上的程序,专门…

新年新挑战:如何用LabVIEW开发跨平台应用

新的一年往往伴随着各种新的项目需求&#xff0c;而跨平台应用开发无疑是当前备受瞩目的发展趋势。在众多开发工具中&#xff0c;LabVIEW 以其独特的图形化编程方式和强大的功能&#xff0c;为开发跨平台应用提供了有效的途径。本文将深入探讨如何运用 LabVIEW 开发能够在不同操…

C 语言实现计算一年中指定日期是第几天 题】

引言 在编程的世界里&#xff0c;处理日期和时间相关的问题是非常常见的。比如在日历应用、任务管理系统、数据分析等场景中&#xff0c;经常需要计算某个日期在一年中是第几天。本文将详细介绍如何使用 C 语言来实现这一功能&#xff0c;通过分析代码的结构、逻辑以及可能存在…

rsync安装与使用-linux015

使用 rsync 可以非常高效地将文件或目录从一个服务器传输到另一个服务器。 能力&#xff1a; 支持 64 位文件、64 位 inode、64 位时间戳、64 位长整型支持套接字对、符号链接、符号链接时间、硬链接、硬链接特殊文件、硬链接符号链接支持 IPv6、访问时间&#xff08;atimes&…