并查集(高阶数据结构)

目录

一、并查集的原理

二、并查集的实现

2.1 并查集的初始化

2.2 查找元素所在的集合

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

2.4 合并两个元素所在的集合

2.5 获取并查集中集合的个数

2.6 并查集的路径压缩

2.7 元素的编号问题

三、并查集题目

3.1 省份的数量

3.2 等式方程的可满足性


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

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

一、并查集的原理

以朋友圈为例,现在有10个人(从0开始编号),刚开始这10个人互不认识,各自属于一个集合

并查集会用一个数组来表示这10个人之间的关系,数组的下标对应就是这10个人的编号,刚开始时数组中的元素都初始化为-1

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

后来这10个人之间通过相互认识,最终形成了三个朋友圈

此时并查集数组中各个位置的值如下

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

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

在根据两个元素合并两个集合时,需先分别找到这两个元素所在集合的根结点,然后再将一个集合合并到另一个集合,并且合并后需要更新数组中根结点的值

合并集合找根结点的原因:

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

二、并查集的实现

实现并查集时通常会实现如下接口:

  • 初始化并查集
  • 查找元素所在的集合
  • 判断两个元素是否在同一个集合
  • 合并两个元素所在的集合
  • 获取并查集中集合的个数
#include <iostream>
#include <vector>
using namespace std;class UnionFindSet
{
public:// 构造函数UnionFindSet(size_t size);// 查找元素所在的集合int FindRoot(int value);// 判断两个元素是否在同一个集合bool IsSameSet(int value1, int value2);// 合并两个元素所在的集合bool Union(int value1, int value2);// 获取并查集中集合的个数size_t GetSetSize();
private:vector<int> _ufs; //维护各个结点间的关系
};

并查集中的数组:

  • 数组的下标依次对应每个元素的编号
  • 数组中元素值为负数,表示下标编号元素为根结点,负数的绝对值表示该集合中元素的个数
  • 数组中元素值为非负数,表示下标编号元素的父结点的编号

2.1 并查集的初始化

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

UnionFindSet(size_t size): _ufs(size, -1) {}

2.2 查找元素所在的集合

查找元素所在的集合,本质就是查找元素所在集合的根结点

查找逻辑如下:

  • 若元素对应下标位置存储的是负数,则说明该元素即为根结点,返回该元素即可
  • 若元素对应下标位置存储的是非负数,则跳转到其父结点的位置继续查找根结点

迭代方式实现:

int FindRoot(int value)
{int root = value;while (_ufs[root] >= 0) root = _ufs[root];return root;
}

递归方式实现:

int FindRoot(int x) {return _ufs[x] < 0 ? x : FindRoot(_ufs[x]);
}

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

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

bool IsSameSet(int value1, int value2) {return FindRoot(value1) == FindRoot(value2);
}

2.4 合并两个元素所在的集合

合并逻辑如下:

  1. 分别找到两个元素所在集合的根结点
  2. 若这两个元素所在集合的根结点相同,则无需合并。若这两个元素所在集合的根结点不同,则将小集合合并到大集合上
  3. 将小集合根结点的值累加到大集合的根结点上,使得大集合根结点的值的绝对值等于两个集合中元素的总数。
  4. 将小集合根结点的值改为大集合根结点的编号,即将小集合的根结点作为大集合根结点的孩子,使得两个集合变为一个集合
bool Union(int value1, int value2)
{int root1 = FindRoot(value1), root2 = FindRoot(value2);//本身在同一个集合,不需合并if (root1 == root2) return false;//合并操作: 数据量小的 往 数据量大的 合并if (abs(_ufs[root1]) < abs(_ufs[root2])) swap(root1, root2);_ufs[root1] += _ufs[root2];_ufs[root2] = root1;return true;
}

说明:当两个集合合并时,尽量将小集合合并到大集合上,因为被合并的那个集合中的所有结点在合并后层数都会加一,所以这样做的目的就是为了让较少的结点层数加一,该操作不是必须的

2.5 获取并查集中集合的个数

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

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

2.6 并查集的路径压缩

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

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

迭代方式实现:

int FindRoot(int value)
{int root = value;while (_ufs[root] >= 0) root = _ufs[root];//路径压缩while (_ufs[value] >= 0){int parent = _ufs[value];_ufs[value] = root;value = parent;}return root;
}

递归方式实现:

int FindRoot(int x) 
{int parent = x; //默认当前结点就是根结点if (_ufs[x] >= 0) { //当前结点值不是负数则继续向上找parent = FindRoot(_ufs[x]); //找到根结点_ufs[x] = parent; //将当前结点的父亲改为根结点(路径压缩)}return parent;
}

2.7 元素的编号问题

上面在实现并查集时,默认元素的编号都是从0开始依次递增的,但用户所给的编号可能并不是从0开始的,也不是连续的,甚至可能不是数字

可以用模板的方式来实现并查集:

  • 在初始化并查集时,根据所给元素建立元素与数组下标之间的映射关系。
  • 在查找元素所在集合的根结点时,先根据所给元素得到其对应的数组下标,然后再进行查找
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;template<class T>
class UnionFindSet
{
public:// 构造函数UnionFindSet(const vector<T>& v): _ufs(v.size(), -1) {for (int i = 0; i < v.size(); ++i)_indexMap[v[i]] = i;}// 查找元素所在的集合int FindRoot(const T& value){int root = _indexMap[value];while (_ufs[root] >= 0) root = _ufs[root];//路径压缩int tmp = _indexMap[value];while (_ufs[tmp] >= 0) {int parent = _ufs[tmp];_ufs[tmp] = root;tmp = parent;}return root;}// 判断两个元素是否在同一个集合bool IsSameSet(const T& value1, const T& value2) {return FindRoot(value1) == FindRoot(value2);}// 合并两个元素所在的集合bool Union(const T& value1, const T& value2){int root1 = FindRoot(value1), root2 = FindRoot(value2);//本身在同一个集合,不需合并if (root1 == root2) return false;//合并操作: 数据量小的 往 数据量大的 合并if (abs(_ufs[root1]) < abs(_ufs[root2])) swap(root1, root2);_ufs[root1] += _ufs[root2];_ufs[root2] = root1;return true;}// 获取并查集中集合的个数size_t GetSetSize(){size_t count = 0;for (int i = 0; i < _ufs.size(); ++i)if (_ufs[i] < 0) ++count;return count;}
private:vector<int> _ufs; //维护各个结点间的关系unordered_map<T, int> _indexMap;//维护元素与下标之间的映射关系
};

再使用并查集时就可以传入任意类型的元素了

int main() 
{vector<string> v = { "张三", "李四", "王五", "赵六", "田七", "周八", "吴九" };UnionFindSet<string> ufs(v);cout << ufs.GetSetSize() << endl; //7ufs.Union("张三", "李四");ufs.Union("王五", "赵六");cout << ufs.GetSetSize() << endl; //5ufs.Union("张三", "赵六");cout << ufs.GetSetSize() << endl; //4return 0;
}

三、并查集题目

3.1 省份的数量

LCR 116. 省份数量 - 力扣(LeetCode)

class Solution 
{
public:int findCircleNum(vector<vector<int>>& isConnected) {vector<int> ufs(isConnected.size(), -1);auto findRoot = [&ufs](int value) {while(ufs[value] >= 0) value = ufs[value];return value;};auto getSetSize = [&ufs]() {size_t count = 0;for(int i = 0; i < ufs.size(); ++i)if(ufs[i] < 0) ++count;return count;};for(size_t i = 0; i < isConnected.size(); ++i)for(size_t j = 0; j < isConnected[0].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;}}return getSetSize();}
};

3.2 等式方程的可满足性

990. 等式方程的可满足性 - 力扣(LeetCode)

class Solution {
public:bool equationsPossible(vector<string>& equations) {vector<int> ufs(26, -1);auto findRoot = [&ufs](int value) {while(ufs[value] >= 0) value = ufs[value];return value;};//第一遍,先把相等的值合并到一个集合中for(auto& str : equations){if(str[1] == '='){int root1 = findRoot(str[0] - 'a');int root2 = findRoot(str[3] - 'a');if(root1 != root2){ufs[root1] += ufs[root2];ufs[root2] = root1;}}}//第二遍,查看不相等的值在不在一个集合,在就相悖,返回falsefor(auto& str : equations){if(str[1] == '!'){int root1 = findRoot(str[0] - 'a');int root2 = findRoot(str[3] - 'a');if(root1 == root2) return false;}}return true;}
};

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

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

相关文章

R-kknn包-类别插值可视化绘制

前面的推文我们介绍了使用scikit-learn结合分类散点数据&#xff0c;构建机器学习分类模型并将模型结果可视化展示&#xff0c;具体链接如下&#xff1a; 机器学习和可视化还能一起这样用&#xff1f;Python教你全搞定。今天这篇推文&#xff0c;我们就使用R语言的kknn包进行类…

【2023地理设计组一等奖】基于机器学习的地下水仿真与时空分析

作品介绍 1 设计思想 1.1 作品背景 华北平原是我国最重要的粮棉产地之一,然而近年来农业的低效用水以及过度压采正逐步加剧其地下水资源的紧张性,为经济可持续发展带来重大风险。而地下水动态变化与人为干预、全球气候波动呈现出高度相关性,因此,地下水的仿真模拟对保障粮…

JMeter 下载、安装、启动

JMeter安装部署依赖Java环境&#xff0c;所以首先得安装JDK。 JDK下载JDK环境变量配置 ① 新建系统环境变量JAVA_HOME ② 编辑系统变量Path ③ 新建系统变量CLASSPATH变量 JMeter下载安装 Apache JMeter - Apache JMeter™ JMeter安装部署依赖Java环境&#xff0c;所以首…

sql注入之GETSHELL

2024.2.1 GETSHELL 利用SQL注入获取MYSQL数据库权限的要求: 文件读写基本要求: 是root用户最高权限 知道网站的绝对路径 文件读写注入的原理&#xff1a; 利用文件的读写权限进行注入&#xff0c;它可以写入一句话木马&#xff0c;也可以读取系统文件的敏感信息 文件读写…

Qt设计师中(没有现成的控件):如何添加QToolBar工具栏

1、在QtCreator设计师界面中,在MainWindow上右键,有“添加工具栏”菜单项 2、但只有在MainWindow上右键才有&#xff0c;在其它控件上方点击则没有&#xff0c;那么怎么在对话框上添加呢&#xff1f; 可以添加一个QWidget&#xff0c;然后手动在ui文件里把class改为QToolBar就…

canvas设置全局透明度globalAlpha(图文示例)

查看专栏目录 canvas实例应用100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

睿尔曼超轻量仿人机械臂—外置按钮盒使用说明

睿尔曼RM系列机械臂的控制方式有很多种&#xff0c;包括&#xff1a;示教器、JSON、API等。在此为大家介绍外置按钮盒的使用方法。 按钮盒接线安装 按钮盒外观如下图所示&#xff0c;有&#xff1a;急停、暂停、开始、继续。四个功能按钮。用户可通过这四个按钮来实现对机械臂运…

环状热力图R语言画法

环状热力图&#xff08;Circular Heatmap&#xff09;是一种以环状布局展示数据的可视化方法。它结合了热力图和极坐标系统&#xff0c;能够有效地显示数据的关系、模式和趋势。 环状热力图通常用于可视化二维数据矩阵&#xff0c;其中行和列代表不同的类别或变量&#xff0c;…

K8S-NFS-StorageClass

工作流程 K8s中部署NFS-StorageClass K8s的StorageClass提供了为集群动态创建PV的能力。 1.部署NFS服务 2.选择NFS的Provinisoner驱动 K8S中没有内置的NFS的制备器&#xff0c;而定义StorageClass的时候需要指定制备器&#xff08;Pervisioner&#xff09;,所以需要&#xf…

OpenHarmony—开发及引用静态共享包(API 9)

HAR(Harmony Archive&#xff09;是静态共享包&#xff0c;可以包含代码、C库、资源和配置文件。通过HAR可以实现多个模块或多个工程共享ArkUI组件、资源等相关代码。HAR不同于HAP&#xff0c;不能独立安装运行在设备上&#xff0c;只能作为应用模块的依赖项被引用。 接下来&a…

springboot146基于Spring Boot的可盈保险合同管理系统的设计与实现

可盈保险合同管理系统 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本可盈保险合同管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时…

富文本编辑器CKEditor4简单使用-02(常用插件安装及使用)

富文本编辑器CKEditor4简单使用-02&#xff08;常用插件安装及使用&#xff09; 1. CKEditor4 入门2. 下载并安装常用插件——Language插件2.1 下载插件2.2 下载并安装 Language 插件2.2.1 下载 Language 插件2.2.2 下载 Menu Button插件以及后续的各依赖插件2.2.3 安装下载的L…

YOLOv5改进 | Conv篇 | 结合Dual思想利用HetConv创新一种全新轻量化结构CSPHet(参数量下降20W,)

一、本文介绍 本文给大家带来的改进机制是我结合Dual的思想利用HetConv提出一种全新的结构CSPHet,我们将其用于替换我们的C3结构,可以将参数降低越20W,GFLOPs降低至4.1GFLOPs,同时本文的结构比我提出的另一个CSPPC精度更高,但是轻量化效果要差一点同时本文结构为我独家创…

ElasticSearch-IK分词器(elasticsearch插件)安装配置和ElasticSearch的Rest命令测试

四、IK分词器(elasticsearch插件) IK分词器&#xff1a;中文分词器 分词&#xff1a;即把一段中文或者别的划分成一个个的关键字&#xff0c;我们在搜索时候会把自己的信息进行分词&#xff0c;会把数据库中或者索引库中的数据进行分词&#xff0c;然后进行一一个匹配操作&…

ElasticSearch概述及Window和Linux环境下安装

一、ElasticSearch 1、ElasticSearch概述 ES&#xff08;Elasticsearch&#xff09;是一个基于开放源代码的分布式搜索引擎&#xff0c;用于快速和灵活地搜索和分析大量数据。它是构建在Apache Lucene之上的&#xff0c;通过提供一个简单而强大的RESTful API来实现全文搜索、…

麒麟系统—— openKylin 安装 Nginx

麒麟系统—— openKylin 安装 Nginx 一、准备工作1. 确保麒麟系统 openKylin 已经安装完毕。 二、下载 nginx三、解压与运行解压检查与编译安装编译运行 四、配置加入到服务中加入环境变量nginx 配置文件 五、常用命令 Nginx 是一款高性能的 HTTP 和反向代理服务器&#xff0c…

Java编程练习之类的封装2

1.封装一个股票&#xff08;Stock&#xff09;类&#xff0c;大盘名称为上证A股&#xff0c;前一日的收盘点是2844.70点&#xff0c;设置新的当前值如2910.02点&#xff0c;控制台既要显示以上信息&#xff0c;又要显示涨跌幅度以及点数变化的百分比。运行效果如下&#xff1a;…

【甲方安全建设】DevOps初体验

文章目录 前言传统的开发方式&#xff1a;Docker-解决环境问题 DevOps-CI/CD走向流水线Jenkins工作流程Git拉取代码Maven构建打包通过SSH连接后端服务器 实现效果 DevSecOps-安全赋能关于安全平台漏洞扫描漏洞预警TODO 前言 临近春节&#xff0c;笔者经过半年北漂&#xff0c;…

Linux系统——防火墙

一、防火墙的认识 引言 安全技术 入侵检测系统&#xff08;Intrusion Detection Systems&#xff09;&#xff1a;特点是不阻断任何网络访问&#xff0c;量化、定位来自内外网络的威胁情况&#xff0c;主要以提供报警和事后监督为主&#xff0c;提供有针对性的指导措施和安全…

企业如何对u盘管控,进行U盘管理?公司电脑禁用U盘的方法

随着信息技术的飞速发展&#xff0c;U盘等移动存储设备已成为企业数据传输和存储的重要工具。然而&#xff0c;U盘的滥用和误操作也带来了巨大的安全隐患&#xff0c;如数据泄露、病毒传播等。 因此&#xff0c;对企业而言&#xff0c;如何有效管控U盘的使用&#xff0c;确保数…