探索数据结构:并查集的分析与实现


✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty’s blog

1. 并查集的引入

1.1 并查集的概念

并查集是一种树型数据结构,主要用于处理不相交集合的合并及查询问题,在实际应用中常常以森林形式呈现。 在众多应用问题里,常常需要将n个不同元素划分成若干不相交的集合。初始状态下,每个元素各自构成一个单元素集合。随后,依据特定规律对归于同一组元素的集合进行合并操作。在此过程中,频繁涉及查询某一元素所属集合的运算。能够很好地描述这类问题的抽象数据结构即并查集。

1.2 并查集的特点

并查集的底层结构一般以数组表示,其一般具有以下三个特点:

  1. 数组的下标对应集合中元素的编号。
  2. 数组中元素如果为负数,代表这个下标是一颗树的根节点,数字的绝对值代表该集合中元素个数。
  3. 数组中原始如果为非负数,代表该元素父节点在数组中的下标。

比如说现在有个一个集合,其中分别有十个元素分别为{0,1,2,3,4,5,6,7,8,9}。现在它们之间毫无任何关系,所以每一个元素都可以单独看成一颗树。如果用数组表示,每个元素对应一个下标,每一个下标对应的值都为-1。

如果对该集合建立关系,比如说分为三个集合:{0,6,7,8}{1,4,9}{2,3,5}。这时我们就可以抽象为对应的三颗子树,对应数组也可更新。

如果我们再让元素0与元素1建立联系,如果这个集合就可合并为一颗子树,对应数组的值也需更新。

2. 并查集的功能

并查集具有常见的以下几个功能:

  1. 初始化并查集。
  2. 查找元素对应的根节点。
  3. 判断两个元素是否在同一个集合。
  4. 合并两个元素所在的集合。
  5. 获取并查集中集合的个数。

3. 并查集的功能实现

3.1 并查集的结构

并查集的结构实现非常简单,直接以数组作为底层结构即可。

//并查集
class UnionFindSet 
{
public://构造函数UnionFindSet(int n);//查找根节点int findRoot(const int x);//判断两个元素是否在同一个集合bool inSameSet(const int x1, const int x2);//合并两个元素所在的集合bool unionSet(const int x1, const int x2);//获取并查集中集合的个数int getNum();
private:vector<int> _ufs; //数组实现
};

3.2 并查集的初始化

通过上面图示我们知道并查集初始化时每一个数据都是一个根节点,所以全部初始化为-1即可。

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

又因为并查集的成员变量都是自定义类型,所以不需要显示写对应的析构函数。

3.3 查找根节点

查找根节点只需要找到对应值为负数的下标即可。

int findRoot(int x)
{int root = x;//如果root大于0则不为根while (_ufs[root] >= 0){root = _ufs[root];}return root;
}

当然我们也可以采用递归的方式实现。

//递归实现
int findRoot(int x) 
{return _ufs[x] < 0 ? x : findRoot(_ufs[x]);
}

3.4 合并两个集合

合并两个集合需要先找到对应的更节点,然后我们默认将小集合合并到大集合上,然后更新大集合的元素个数,以及小集合的根节点。

//合并两个集合
bool unionSet(const int x1, const int x2)
{int root1 = findRoot(x1);int root2 = findRoot(x2);if (root1 == root2){return false;}//将小集合拼接到大集合上//让root1代表大集合,root2代表小集合if (_ufs[root1] > _ufs[root2]){swap(root1, root2);}//大集合的元素个数增加_ufs[root1] += _ufs[root2];//小集合的父节点改为大集合_ufs[root2] = root1;return true;
}

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

我们只需找到两元素的根节点判断是否相同即可。

//判断两个元素是否在同一个集合
bool inSameSet(const int x1, const int x2)
{int root1 = findRoot(x1);int root2 = findRoot(x2);return root1 == root2;
}

3.6 获取并查集的集合个数

获取集合个数即判断有几个根节点。

//获取集合的个数
int getNum()
{int count = 0;//遍历数组如果小于0则为根for (auto& e : _ufs){if (e < 0){++count;}}return count;
}

4. 并查集的优化

4.1 路径压缩

在我们不断将两个集合合并的过程中,可能会出现某种极端的情况,使查找对应的根节点的效率接近线性,大大降低我们的查找效率。为了避免发生这种情况,我们可以采用一种路径压缩的形式。

路径压缩简单来说就是把节点都与根节点直接相连,减少查找效率。

路径压缩一般我们都在查找中顺便实现。

int findRoot(int x)
{int root = x;//如果root大于0则不为根while (_ufs[root] >= 0){root = _ufs[root];}//路径压缩while (_ufs[x] >= 0){//记录父节点int parent = _ufs[x];//指向根节点_ufs[x] = root;x = parent;}return root;
}

当然我们也可以采用递归实现。

//递归查找
int findRoot(int x) 
{int root = x; if (_ufs[x] >= 0) { root = findRoot(_ufs[x]); //找到根结点_ufs[x] = root; //进行路径压缩}return root; //返回根结点
}

4.2 泛型编程

我们可以利用模版实现一个针对不同类型的并查集,而为了方便不同类型映射方便,我们还需要增加一个成员变量来映射元素与下标之间的联系。

template<class T>
class UnionFindSet
{
public:
private:vector<int> _ufs;//并查集的底层为数组unordered_map<T, int> _indexMap;//建立元素与下标直接的练习
};

然后我们可以先初始化时映射数据与下标的关系,查找时可以直接通过数据找到下标。

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& x)
{//找到对应下标int root = _indexMap[x];//如果root大于0则不为根while (_ufs[root] >= 0){root = _ufs[root];}//路径压缩int index = _indexMap[x];while (_ufs[index] >= 0){//记录父节点int parent = _ufs[index];//指向根节点_ufs[index] = root;index = parent;}return root;
}

5. 复杂度分析

以下是对上述并查集代码中各个操作的时间复杂度与空间复杂度分析:
二、时间复杂度分析

  1. 并查集的初始化:

整体时间复杂度为 O ( n ) O(n) O(n)

  • 初始化 _ufs 为大小 v.size() 的向量,时间复杂度为 O ( n ) O(n) O(n)
  • 通过遍历 v 建立 _indexMap,时间复杂度为 O ( n ) O(n) O(n),其中 n n n 是输入向量 v 的大小。
  1. 并查集的查找根节点操作:

整体时间复杂度接近 O ( 1 ) O(1) O(1)

  • 第一次查找对应下标的时间复杂度为 O ( 1 ) O(1) O(1),因为 unordered_map 的查找操作平均时间复杂度为 O ( 1 ) O(1) O(1)
  • 路径压缩过程中,最坏情况下需要遍历整个集合,时间复杂度为 O ( l o g n ) O(log n) O(logn),其中 n n n 是集合的大小,但由于路径压缩的效果,后续查找操作会更快,实际平均时间复杂度接近 O ( 1 ) O(1) O(1)
  1. 并查集合并两个集合操作:

整体时间复杂度接近 O ( 1 ) O(1) O(1)

  • 查找两个元素的根节点,时间复杂度接近 O ( 1 ) O(1) O(1)(因为调用了 findRoot)。
  • 合并操作时间复杂度为 O ( 1 ) O(1) O(1)
  1. 判断两个元素是否在同一个集合操作:

整体时间复杂度接近 O ( 1 ) O(1) O(1)

  • 查找两个元素的根节点,时间复杂度接近 O ( 1 ) O(1) O(1)(因为调用了 findRoot)。
  • 比较两个根节点是否相同,时间复杂度为 O ( 1 ) O(1) O(1)
  1. 获取并查集的集合个数操作:

整体时间复杂度为 O ( n ) O(n) O(n),但通常情况下 n 较大时这个操作执行次数较少。

  • 遍历 _ufs 向量,时间复杂度为 O ( n ) O(n) O(n),其中 n n n 是输入向量 v 的大小。

二、空间复杂度分析

  1. 并查集整体的空间复杂度取决于两个数据结构:
  • vector<int> _ufs:存储每个元素的父节点信息或集合大小(负数表示根节点且绝对值为集合大小)。其空间复杂度为 O ( n ) O(n) O(n),其中 n n n 是输入向量 v 的大小。
  • unordered_map<T, int> _indexMap:建立元素与下标之间的映射。其空间复杂度取决于输入元素的数量,最坏情况下为 O ( n ) O(n) O(n),其中 n n n 是输入向量 v 的大小。

所以,整体空间复杂度为 O ( n ) O(n) O(n),其中 n n n 是输入向量 v 的大小。

综上所述,并查集的大部分操作时间复杂度接近 O ( 1 ) O(1) O(1),空间复杂度为 O ( n ) O(n) O(n)

6. 源码

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& x){//找到对应下标int root = _indexMap[x];//如果root大于0则不为根while (_ufs[root] >= 0){root = _ufs[root];}//路径压缩int index = _indexMap[x];while (_ufs[index] >= 0){//记录父节点int parent = _ufs[index];//指向根节点_ufs[index] = root;index = parent;}return root;}//合并两个合集bool unionSet(const T& x1, const T& x2){int root1 = findRoot(x1);int root2 = findRoot(x2);if (root1 == root2){return false;}//将小集合拼接到大集合上//让root1代表大集合,root2代表小集合if (_ufs[root1] > _ufs[root2]){swap(root1, root2);}//大集合的元素个数增加_ufs[root1] += _ufs[root2];//小集合的父节点改为大集合_ufs[root2] = root1;return true;}//判断两个元素是否在同一个集合bool inSameSet(const int x1, const int x2){int root1 = findRoot(x1);int root2 = findRoot(x2);return root1 == root2;}//获取并查集的个数int getNum(){int count = 0;//遍历数组如果小于0则为根for (auto& e : _ufs){if (e < 0){++count;}}return count;}
private:vector<int> _ufs;//并查集的底层为数组unordered_map<T, int> _indexMap;//建立元素与下标直接的练习
};

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

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

相关文章

什么是品牌低价 低价要如何处理

在消费领域&#xff0c;当消费者遭遇商品价格混乱不堪的局面&#xff0c;他们大可以选择敬而远之&#xff0c;放弃购买。但对于品牌商而言&#xff0c;若线上出现低价、乱价的情况&#xff0c;若坐视不管&#xff0c;那必然会引发一系列严重后果。 品牌若是对线上的低价、乱价现…

Linux环境下OpenSSH升级到 OpenSSH_9.8p1(内置保姆级教程并包含openssl升级过程)

文章目录 前言一、下载openssh、openssl二进制包二、升级步骤1.系统开启telnet&#xff0c;防止意外导致shh无法连接2.确认升级前openssh的版本3.升级openssh3.1.备份旧ssh配置文件及目录3.2.备份旧ssh相关的二进制程序文件3.3.安装gcc,并解压9.8p1的安装包3.4.执行openssh编译…

Kali Linux 三种网络攻击方法总结(DDoS、CC 和 ARP 欺骗)

一、引言 在当今数字化的时代&#xff0c;网络安全成为了至关重要的议题。了解网络攻击的方法和原理不仅有助于我们增强防范意识&#xff0c;更是网络安全领域专业人员必备的知识。Kali Linux 作为一款专为网络安全专业人员和爱好者设计的操作系统&#xff0c;提供了丰富的工具…

ES5到ES6 js的语法更新

js是一门弱语言类型&#xff0c;为了实现更有逻辑的代码&#xff0c;需要不断更新语法规范&#xff0c;es就是用来规范js语法的标准。 09年发布了es5&#xff0c;到15年发布es6&#xff0c;到现在es6泛指es5.1以后的版本es2016&#xff0c;es2017。 var、let、const 关键字&…

【C++】OJ习题(初阶)

&#x1f680;个人主页&#xff1a;奋斗的小羊 &#x1f680;所属专栏&#xff1a;C 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 &#x1f4a5;1、字符串&#x1f4a5;1.1 字符串相加&#x1f4a5;1.2 验证回文字符串&#x1f4a5;1.3 反转…

电商平台的推荐算法需要备案吗?

答案是肯定的&#xff01; 政策要求&#xff1a; 根据我国《互联网信息服务算法推荐管理规定》&#xff08;以下简称《规定》&#xff09;第六条&#xff0c;具有舆论属性或社会动员能力的互联网信息服务&#xff0c;包括电商平台的推荐算法&#xff0c;需要进行备案。 电商平…

ubuntu24.04安装nginx1.24

ubuntu安装nginx 更新包索引 sudo apt update安装nginx sudo apt install nginx确认安装成功并检查Nginx版本 nginx -v启动Nginx服务 sudo systemctl start nginx设置Nginx开机自启 sudo systemctl enable nginx在浏览器中访问 http://<your_server_IP> 来确认Nginx…

Linux学习记录(十二)————共享内存

文章目录 5.共享内存1.概念2.相关函数共享内存映射共享内存进程间的通信 5.共享内存 1.概念 共享内存&#xff08;Shared Memory&#xff09;就是允许多个进程访问同一个内存空间&#xff0c;是在多个进程之间共享和传递数据最 高效的方式。操作系统将不同进程之间共享内存安排…

HTML5+JavaScript绘制彩虹和云朵

HTML5JavaScript绘制彩虹和云朵 彩虹&#xff0c;简称虹&#xff0c;是气象中的一种光学现象&#xff0c;当太阳光照射到半空中的水滴&#xff0c;光线被折射及反射&#xff0c;在天空上形成拱形的七彩光谱&#xff0c;由外圈至内圈呈红、橙、黄、绿、蓝、靛、紫七种颜色。事实…

【流媒体】RTMPDump—RTMP_ConnectStream(创建流连接)

目录 1. RTMP_ConnectStream函数1.1 读取packet&#xff08;RTMP_ReadPacket&#xff09;1.2 解析packet&#xff08;RTMP_ClientPacket&#xff09;1.2.1 设置Chunk Size&#xff08;HandleChangeChunkSize&#xff09;1.2.2 用户控制信息&#xff08;HandleCtrl&#xff09;1…

嵌入式开发就业方向有哪些?前景未来可期!

在科技日新月异的今天&#xff0c;嵌入式系统几乎渗透到了我们生活的各个角落。从简单的家用电器到复杂的工业自动化设备&#xff0c;再到我们手中的智能手机&#xff0c;无一不体现出嵌入式技术的魅力。因此&#xff0c;嵌入式领域的就业前景广阔&#xff0c;为众多求职者提供…

职场难题怎么破?六西格玛培训给你答案!

在当今追求高效与卓越的职场环境中&#xff0c;六西格玛培训如同一股强劲的东风&#xff0c;为众多职场人士带来了提升自我、突破瓶颈的契机。作为起源于摩托罗拉、在通用电气得到广泛应用的管理方法论&#xff0c;六西格玛以其严谨的数据分析、持续的流程优化和卓越的质量提升…

公认最强充电宝有哪些?盘点四款公认强悍级别充电宝推荐

随着智能手机和其他移动设备的广泛应用&#xff0c;充电宝已经成为我们生活中不可或缺的一部分。然而&#xff0c;市场上众多品牌和型号的充电宝也让消费者面临选择难题&#xff0c;安全隐患也随之浮现。因此&#xff0c;选择一款安全可靠、性能卓越的充电宝显得尤为重要。本文…

[数据集][目标检测]起子检测数据集VOC+YOLO格式1215张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1215 标注数量(xml文件个数)&#xff1a;1215 标注数量(txt文件个数)&#xff1a;1215 标注…

python逻辑控制 学习

if 语句 普通if &#xff0c;与多条件语句 #! /usr/bin/python3 age int(input("请输入你的年龄&#xff1a;")) print("你今年", age, "岁了。") if age < 18:print("你还未成年&#xff0c;请多加努力&#xff01;") elif age …

[802.11e]WMM

WMM概念 WiFi WMM&#xff08;无线多媒体&#xff09;是一种用于无线局域网&#xff08;WLAN&#xff09;的QoS&#xff08;服务质量&#xff09;标准。WMM旨在提供更好的网络性能&#xff0c;特别是在传输多媒体内容&#xff08;如音频和视频&#xff09;时。它通过对不同类型…

Halcon20.11深度学习目标检测模型

1.前言&#xff1a;.Halcon的深度学习标注工具一直在更新&#xff0c;我下载的20.11版本的Deep Learning Tool已经显示过期&#xff0c;无奈只能下载最新版MVTec Deep Learning Tool 24.05。不过最新版的标注工具做的很人性化&#xff0c;分类&#xff0c;目标检测&#xff0c;…

获取阿里云Docker镜像加速器地址

注册并登录阿里云账号&#xff1a;首先&#xff0c;你需要有一个阿里云账号。如果还没有&#xff0c;可以在阿里云官网注册。 访问容器镜像服务&#xff1a;登录后&#xff0c;进入“产品与服务”&#xff0c;找到“容器服务”或“容器镜像服务”。阿里云容器服务 找到镜像加…

iOS开发进阶(二十二):Xcode* 离线安装 iOS Simulator

文章目录 一、前言二、模拟器安装 一、前言 Xcode 15 安装包的大小相比之前更小&#xff0c;因为除了 macOS 的 Components&#xff0c;其他都需要动态下载安装&#xff0c;否则提示 iOS 17 Simulator Not Installed。 如果不安装对应的运行模拟库&#xff0c;真机和模拟器无法…

【UE】关卡实例基本介绍与使用

目录 一、什么是关卡实例 二、创建关卡实例 三、编辑关卡实例 四、破坏关卡实例 五、创建关卡实例蓝图 一、什么是关卡实例 关卡实例本质上是一个已存在关卡的可重复使用的实例化版本。它基于原始关卡&#xff0c;但可以在运行时进行独立的修改和定制&#xff0c;同时保持…