【算法】并查集的介绍与使用
1.并查集的概论
定义:
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。
主要构成:
并查集主要由一个整型数组pre[ ]和两个函数find( )、join( )构成。
数组 pre[ ] 记录了每个点的前驱节点是谁,函数 find(x) 用于查找指定节点 x 属于哪个集合,函数 join(x,y) 用于合并两个节点 x 和 y 。
作用:
并查集的主要作用是求连通分支数(如果一个图中所有点都存在可达关系(直接或间接相连),则此图的连通分支数为1;如果此图有两大子图各自全部可达,则此图的连通分支数为2……)
2.用一个故事来引入并查集(“魔法学院的联盟”)
在遥远的阿尔多王国,有一座声名远扬的魔法学院——天星学院。这所学院不仅教授强大的魔法,还拥有许多古老的传说。传说中提到,曾经有一个神秘的魔法师,名叫艾尔文,他发明了一种神奇的魔法工具,叫做“并查集”。
故事发生在天星学院的一个风和日丽的早晨,学院的学生们正忙于准备即将到来的年度魔法比赛。比赛的内容是让学生们组成小组进行各种挑战,争夺“学院最佳团队”的荣誉。
艾伦和他的朋友们被分配到了同一个小组,但在比赛前的准备中,他们发现学院里有多个小组已经相互合作,形成了若干个强大的联盟。这些联盟的存在让他们感到有些困惑,因为他们不知道如何将所有的合作伙伴都联系起来,从而提高自己的团队实力。
就在他们为此感到苦恼时,学院的长者向他们介绍了一种古老的魔法——并查集。长者解释说,并查集是一种强大的魔法工具,可以帮助他们轻松地管理和合并不同的联盟。
艾伦和他的朋友们认真听取了长者的讲解,并决定尝试使用这种魔法。他们发现,并查集的核心在于两个主要操作:
- 合并(Union):当两个小组决定合作时,他们可以通过这个操作将两个小组合并成一个更强大的联盟。
- 查找(Find):当他们需要知道某个小组是否已经与其他小组在同一个联盟中时,可以使用这个操作来检查。
使用了并查集的魔法后,艾伦和他的团队发现管理各个小组变得更加简单。他们能够迅速地找到已经存在的联盟,并根据需要进行合并。这使得他们能够更好地协调和组织自己的资源,形成了一个强大的联盟。
随着比赛的进行,艾伦和他的团队利用并查集的魔法成功地将所有的小组整合到一起,形成了一个无敌的联盟。他们在比赛中表现出色,最终赢得了“学院最佳团队”的荣誉。
在获胜的庆祝会上,艾伦和他的朋友们感慨万千,他们深刻理解了并查集魔法的强大力量,也明白了团队合作的重要性。从那以后,天星学院的学生们都学会了如何使用并查集来解决各种复杂的联盟问题,而艾伦和他的团队也因为这次经历成为了学院的传奇人物。
3.从数据结构的方向看并查集
-
查找(Find):确定一个元素属于哪个集合,通常返回集合的代表元素或根节点。此操作可以通过路径压缩优化,使得树的高度保持较小,从而提高查找效率。
-
合并(Union):将两个集合合并为一个集合。合并操作可以通过按秩合并(按树的深度合并)或按大小合并(合并小的树到大的树)来优化,从而保持数据结构的效率。
并查集常用于处理动态连通性问题,例如在图论中的连通分量计算。通过这些操作,并查集能够在接近常数时间内完成集合的合并和查找。
3.find()函数的定义与实现
find()
函数用于确定一个元素所在的集合的代表元素或根节点。它通常通过路径压缩优化来提高效率。
开始时每个集合都是一个独立的集合,并且都是等于自己本身下标的数
例如:
p[5]=5,p[3]=3;
如果是M操作的话那么就将集合进行合并,合并的操作是:
p[3]=p[5]=5;
所以3的祖宗节点便成为了5
此时以5为祖宗节点的集合为{5,3}
如果要将p[9]=9插入到p[3]当中,应该找到3的祖宗节点,
然后再把p[9]=9插入其中,所以p[9]=find(3);(find()函数用于查找祖宗节点)
也可以是p[find(9)]=find(3),因为9的节点本身就是9
此时以5为祖宗节点的集合为{5,3,9};
如果碰到多个数的集合插入另一个集合当中其原理是相同的
例如:
上述中以5为祖宗节点的是p[5],p[3],p[9];(即p[5]=5,p[3]=5,p[9]=5)
再构造一个以6为祖宗节点的集合为{6,4,7,10}
如果要将以6为祖宗节点的集合插入到以5为祖宗节点的集合,则该操作可以是
p[6]=find(3)(或者find(9),find(5))
此时p[6]=5
当然如果是以6为祖宗节点集合中的4,7,10则可以这样
p[find(4)]=find(3)
或者p[find(7)]=find(3)均可以
此时以6为祖宗节点的集合的祖宗节点都成为了5
3.2find()函数的实现
第一个find函数同时也运用了状态压缩即:
find 函数不仅有找祖宗的功能,还把这个查找路径上所有节点直接变成了祖宗节点的孩子
int find(int x)
{if(p[x]!=x) p[x]=find(p[x]);/*经上述可以发现,每个集合中只有祖宗节点的p[x]值等于他自己,即:p[x]=x;*/return p[x];//找到了便返回祖宗节点的值
}2.
int find(int x) //查找x的祖宗结点
{while(pre[x] != x) //如果x的上级不是祖宗节点x = pre[x]; //x继续找return x;
}
4.合并集合的代码实现与逻辑
- 先查找两个元素
x
和y
的根节点。- 比较两个根节点的秩(高度),将秩较小的根节点的树合并到秩较大的根节点的树下,确保树的高度尽可能低。
- 如果两个根节点的秩相同,将其中一个根节点设置为另一个根节点的子节点,并将该根节点的秩加一。
//合并a b所在的两个集合
void merge(int a, int b)
{int pa = find(a);//找到 a 所在集合的代表元素int pb = find(b);//找到 b 所在集合的代表元素if(pa != pb)//如果不是同一个,则属于不同集合,需要合并{p[pa] = pb;//将a所在集合代表元素的代表元素设置为b所在集合的代表元素。}
}
5.并查集的测试代码(进行了按秩合并的写法)
#include <vector>
#include <iostream>class DisjointSet {
public:DisjointSet(int size) : parent(size), rank(size, 1) {// 初始化每个元素的父节点指向自己for (int i = 0; i < size; ++i) {parent[i] = i;}}// 查找元素 x 所在集合的根节点,并进行路径压缩int find(int x) {if (parent[x] != x) {parent[x] = find(parent[x]); // 路径压缩}return parent[x];}// 合并两个集合void unionSets(int x, int y) {int rootX = find(x); // 查找 x 的根节点int rootY = find(y); // 查找 y 的根节点if (rootX != rootY) {// 按秩合并:将秩较低的树合并到秩较高的树下if (rank[rootX] > rank[rootY]) {parent[rootY] = rootX;} else if (rank[rootX] < rank[rootY]) {parent[rootX] = rootY;} else {parent[rootY] = rootX;rank[rootX] += 1;}}}private:std::vector<int> parent; // 记录每个元素的父节点std::vector<int> rank; // 记录每个树的秩
};int main() {DisjointSet ds(10); // 初始化一个包含10个元素的并查集ds.unionSets(1, 2); // 合并集合 {1} 和 {2}ds.unionSets(2, 3); // 合并集合 {1, 2} 和 {3}ds.unionSets(4, 5); // 合并集合 {4} 和 {5}// 检查合并后的结果std::cout << "Find 1: " << ds.find(1) << std::endl; // 输出 1std::cout << "Find 3: " << ds.find(3) << std::endl; // 输出 1std::cout << "Find 4: " << ds.find(4) << std::endl; // 输出 4std::cout << "Find 5: " << ds.find(5) << std::endl; // 输出 4ds.unionSets(3, 5); // 合并集合 {1, 2, 3} 和 {4, 5}// 检查合并后的结果std::cout << "Find 5 after union with 3: " << ds.find(5) << std::endl; // 输出 1,因为 1 和 5 现在在同一集合中return 0;
}
6.简单的实现代码(没有使用按秩合并的写法)
#include <vector>
#include <iostream>class DisjointSet {
public:DisjointSet(int size) : parent(size) {for (int i = 0; i < size; ++i) {parent[i] = i; // 每个元素的父节点初始化为自身}}// 查找元素 x 所在集合的根节点,并进行路径压缩int find(int x) {if (parent[x] != x) {parent[x] = find(parent[x]); // 路径压缩}return parent[x];}// 合并 a 和 b 所在的两个集合void merge(int a, int b) {int rootA = find(a); // 查找 a 所在集合的代表元素int rootB = find(b); // 查找 b 所在集合的代表元素if (rootA != rootB) {parent[rootB] = rootA; // 直接将 b 的根节点挂到 a 的根节点下}}private:std::vector<int> parent; // 记录每个元素的父节点
};int main() {DisjointSet ds(10); // 初始化一个包含10个元素的并查集ds.merge(1, 2); // 合并集合 {1} 和 {2}ds.merge(2, 3); // 合并集合 {1, 2} 和 {3}ds.merge(4, 5); // 合并集合 {4} 和 {5}// 检查合并后的结果std::cout << "Find 1: " << ds.find(1) << std::endl; // 输出 1std::cout << "Find 3: " << ds.find(3) << std::endl; // 输出 1std::cout << "Find 4: " << ds.find(4) << std::endl; // 输出 4std::cout << "Find 5: " << ds.find(5) << std::endl; // 输出 4ds.merge(3, 5); // 合并集合 {1, 2, 3} 和 {4, 5}// 检查合并后的结果std::cout << "Find 5 after union with 3: " << ds.find(5) << std::endl; // 输出 1,因为 1 和 5 现在在同一集合中return 0;
}
7.一些基本操作的实现
const int N=1005 //指定并查集所能包含元素的个数(由题意决定)
int pre[N]; //存储每个结点的前驱结点
int rank[N]; //树的高度
void init(int n) //初始化函数,对录入的 n个结点进行初始化
{for(int i = 0; i < n; i++){pre[i] = i; //每个结点的上级都是自己 rank[i] = 1; //每个结点构成的树的高度为 1 }
}
int find(int x) //查找结点 x的根结点
{if(pre[x] == x) return x; //递归出口:x的上级为 x本身,则 x为根结点 return find(pre[x]); //递归查找
} int find(int x) //改进查找算法:完成路径压缩,将 x的上级直接变为根结点,那么树的高度就会大大降低
{if(pre[x] == x) return x; //递归出口:x的上级为 x本身,即 x为根结点 return pre[x] = find(pre[x]); //此代码相当于先找到根结点 rootx,然后 pre[x]=rootx
} bool isSame(int x, int y) //判断两个结点是否连通
{return find(x) == find(y); //判断两个结点的根结点(即代表元)是否相同
}bool join(int x,int y)
{x = find(x); //寻找 x的代表元y = find(y); //寻找 y的代表元if(x == y) return false; //如果 x和 y的代表元一致,说明他们共属同一集合,则不需要合并,返回 false,表示合并失败;否则,执行下面的逻辑if(rank[x] > rank[y]) pre[y]=x; //如果 x的高度大于 y,则令 y的上级为 xelse //否则{if(rank[x]==rank[y]) rank[y]++; //如果 x的高度和 y的高度相同,则令 y的高度加1pre[x]=y; //让 x的上级为 y}return true; //返回 true,表示合并成功
}
相关文章:

【算法】并查集的介绍与使用
1.并查集的概论 定义: 并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。 主要构成: …...
Shell——运算符
在 Shell 编程中,运算符用于执行各种类型的操作,如算术运算、字符串比较、文件测试等。以下是 Shell 中常用的运算符分类和示例: 1. 算术运算符 Shell 中使用 expr 或 $(( ... )) 来进行算术运算。 : 加法-: 减法*: 乘法/: 除法%: 取余**:…...

SweetAlert2
1. SweetAlert2 SweetAlert2是一个基于JavaScript的库, 用于在网页上替换标准的警告框(alert), 确认框(confirm)和提示框(prompt), 并提供更加美观和用户友好的界面.需要在项目中引入SweetAlert2, 可以通过CDN链接或者将库文件下载到你的项目中来实现这一点. 通过CDN引入:<…...

c语言中比较特殊的输入函数
目录 一.getchar()函数 1.基本功能 2.使用方法 (1).读取单个字符 (2).读取多个字符(直到遇到换行符) (3).处理输入中的空白字符 3.返回值 4.应用场景 5.注意事项 二.fgets()函数 1.函数原型 2.工作原理 3.使用示例 (1).从标准输入读取一行…...

Java版自动化测试之Selenium
1. 准备 编程语言:Java JDK版本:17 Maven版本:3.6.1 2. 开始 声明:本次只测试Java的Selenium自动化功能 本次示例过程:打开谷歌游览器,进入目标网址,找到网页的输入框元素,输入指…...
【计算机网络】——计算机网络的性能指标
速率(speed) 连接在计算机网络上的主机在数字信道上传送数据的速率。 影响条件: 带宽(band width) 指在固定的时间可传输的资料数量 单位:bps或HZ 吞吐量(throughtput) 指对网络、…...
MongoDB数据类型介绍
MongoDB作为一种高性能、开源、无模式的文档型数据库,支持丰富的数据类型,以满足各种复杂的数据存储需求。本文将详细介绍MongoDB支持的主要数据类型,包括数值类型、字符串类型、日期和时间类型、布尔类型、二进制类型、数组、对象以及其他扩…...

【SpringBoot】SpringBoot 中 Bean 管理和拦截器的使用
目录 1.Bean管理 1.1 自定义Bean对象 1.2 Bean的作用域和生命周期 2.拦截器的使用 1.Bean管理 默认情况下,Spring项目启动时,会把我们常用的Bean都创建好放在IOC容器中,但是有时候我们自定义的类需要手动配置bean,这里主要介绍…...

Spring IoCDI(中)--IoC的进步
通过上文的讲解和学习, 我们已经知道了Spring IoC 和DI的基本操作, 接下来我们来系统的学习Spring IoC和DI 的操作. 前⾯我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对 象,也就是bean的存储。 1. Bean的…...

读软件开发安全之道:概念、设计与实施02经典原则
1. CIA原则 1.1. 软件安全都构建在信息安全的三大基本原则之上,即机密性(confidentiality)、完整性(integrity)和可用性(availability) 1.2. 双方交换的数据 1.2.1. 从技术上看,端点之间的数据交换本身就会削弱交互的机密性 1.2.2. 隐藏通信数据量的一…...
MySQL中处理JSON数据:大数据分析的新方向,详解与示例
文章目录 1. MySQL中的JSON数据类型2. JSON函数和运算符3. 创建JSON列的表4. 插入JSON数据5. 查询JSON数据6. 复杂查询和聚合7. JSON 数据的索引8. 总结 在当今的大数据时代,JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式&a…...

【图形学】TA之路-矩阵
在 Unity 中,矩阵广泛用于处理各种图形变换,例如平移、旋转、缩放等。矩阵的使用不仅限于三维空间,还可以应用于二维空间的操作。了解矩阵及其运算对于游戏开发和计算机图形学非常重要。Unity 中使用的是行向量不是列向量,这个要注…...

LAMM: Label Alignment for Multi-Modal Prompt Learning
系列论文研读目录 文章目录 系列论文研读目录文章题目含义AbstractIntroductionRelated WorkVision Language ModelsPrompt Learning MethodologyPreliminaries of CLIPLabel AlignmentHierarchical Loss 分层损失Parameter Space 参数空间Feature Space 特征空间Logits Space …...
mac编译opencv 通用架构库的记录
1,通用架构 (x86_64;arm64)要设置的配置项: CPU_BASELINE CPU_DISPATCH 上面这两个我设置成SSE_3,其他选项未尝试,比如不设置。 CMAKE_OSX_ARCHITECTURES:x86_64;arm64 WITH_IPP:不勾选 2,contrib库的添加: 第一次…...
Python 向IP地址发送字符串
Python 向IP地址发送字符串 在网络编程中,使得不同设备间能够进行数据传输是一项基本任务。Python提供了强大的库,帮助开发者轻松地实现这种通信。本文将介绍如何使用Python通过UDP协议向特定的IP地址发送字符串信息。 UDP协议简介 UDP(用…...
上升响应式Web设计:纯HTML和CSS的实现技巧-1
响应式Web设计(Responsive Web Design, RWD)是一种旨在确保网站在不同设备和屏幕尺寸下都能良好运行的网页设计策略。通过纯HTML和CSS实现响应式设计,主要依赖于媒体查询(Media Queries)、灵活的布局、可伸缩的图片和字…...

利用java结合python实现gis在线绘图,主要技术java+python+matlab+idw+Kriging
主要技术javapythonmatlabidwKriging** GIS中的等值面和等高线绘图主要用于表达连续空间数据的分布情况,特别适用于需要展示三维空间中某个变量随位置变化的应用场景。 具体来说,以下是一些适合使用GIS等值面和等高线绘图的场景: 地形与地貌…...

Android全面解析之context机制(三): 从源码角度分析context创建流程(下)
前言 前面已经讲了什么是context以及从源码角度分析context创建流程(上)。限于篇幅把四大组件中的广播和内容提供器的context获取流程放在了这篇文章。广播和内容提供器并不是context家族里的一员,所以他们本身并不是context,因而…...
执行docker compose命令出现 Additional property include is not allowed
问题背景 在由docker-compose.yml的文件目录下执行命令 docker compose up -d 出现错误 Additional ininoperty include is not allowed 原因 我的docker-compose.yml 文件中出现了include标签旧版本的docker-compose 不支持此标签 解决办法 下载支持的docker-compose 解决…...

STM32通过I2C硬件读写MPU6050
目录 STM32通过I2C硬件读写MPU6050 1. STM32的I2C外设简介 2. STM32的I2C基本框图 3. STIM32硬件I2C主机发送流程 10位地址与7位地址的区别 7位主机发送的时序流程 7位主机接收的时序流程 4. STM32硬件与软件的波形对比 5. STM32配置硬件I2C外设流程 6. STM32的I2C.h…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...

回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...

【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...