并查集(不相交集)详解
目录
一.并查集
1.什么是并查集
2.并查集的基本操作
3.并查集的应用
4.力扣上的题目
二.三大操作
1.初始化
2.查找
3.合并
三.省份数量
1.题目描述
2.问题分析
3.代码实现
四.冗余连接
1.题目描述
2.问题分析
3.代码实现
一.并查集
1.什么是并查集
并查集(Disjoint-set Union 或 Union-find)是一种数据结构,用于维护一些不相交(disjoint)的集合,支持合并两个集合以及判断两个元素是否属于同一个集合。
并查集可以使用树来实现,每个集合可以看做是一棵树,代表元素是根节点。使用路径压缩可以减少查找操作的时间复杂度,使用按秩合并可以减少合并操作的时间复杂度,使得并查集的时间复杂度可以达到近乎常数级别,因此在一些算法中广泛应用,比如 Kruskal 算法和 Tarjan 算法。
2.并查集的基本操作
- 初始化:将每个元素初始化为单独的集合,每个集合的代表元素就是自己;
- 查找:给定一个元素,找到它所属的集合的代表元素;
- 合并:将两个集合合并成一个集合,即将其中一个集合的代表元素作为另一个集合的代表元素的子节点。
3.并查集的应用
并查集是一种用于维护集合(组)的数据结构,它通常用于解决一些离线查询、动态连通性和图论等相关问题。
其中最常见的应用场景是解决图论中的连通性问题,例如判断图中两个节点是否连通、查找图的连通分量、判断图是否为一棵树等等。并查集可以快速地合并两个节点所在的集合,以及查询两个节点是否属于同一个集合,从而有效地判断图的连通性。
并查集还可以用于解决一些离线查询问题,例如静态连通性查询和最小生成树问题,以及一些动态连通性问题,例如支持动态加边和删边的连通性问题。
总之,如果需要维护集合(组)的连通性信息,就可以考虑使用并查集。
现在举一个现实中的问题,判断一个人是否属于一个家族,通常一个家族里面两个人可能不是彼此认识的,但是A和B是亲属关系,B和C是亲属关系,此时我们可以判断出A和C就是亲属关系

由于家族关系可能错综复杂,这个时候我们是否可以找一个代表,这个人来代表这个家族,比如选择A来代表家族A,D来代表家族B,那么只要你和A有关系,你就是属于A家族的,你和D有关系,你就是属于B家族的
并查集就是解决这样的问题的.
4.力扣上的题目
以下是一些力扣(LeetCode)上关于并查集(Union Find)的题目:
-
朋友圈(547) https://leetcode-cn.com/problems/friend-circles/
-
冗余连接(684) https://leetcode-cn.com/problems/redundant-connection/
-
冗余连接 II(685) https://leetcode-cn.com/problems/redundant-connection-ii/
-
岛屿数量(200) https://leetcode-cn.com/problems/number-of-islands/
-
连通网络的操作次数(1319) https://leetcode-cn.com/problems/number-of-operations-to-make-network-connected/
-
表示数值的字符串(剑指 Offer 20) https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/
-
最大连通面积(面试题 16.19) https://leetcode-cn.com/problems/pond-sizes-lcci/
-
能否连接形成区域(面试题 16.19) https://leetcode-cn.com/problems/pond-sizes-lcci/
-
合并账户(721) https://leetcode-cn.com/problems/accounts-merge/
-
判断能否形成等差数列(1502) https://leetcode-cn.com/problems/can-make-arithmetic-progression-from-sequence/
以上是一些力扣上的并查集题目,建议在练习时,先自己尝试思考解法,如果卡在某个地方,可以查看题解或者向其他程序员求助。
二.三大操作
1.初始化
我们把数组初始化-1,具体的作用我们之后再来分析具体的用处,具体含义就是,这个负数的相反数为这棵树的高度-1

public void init(int[] parent) {//初始值设置为-1for (int i = 0; i < parent.length; ++i) {parent[i] = -1;}}
2.查找
不进行路径压缩的查找简单粗暴的,它的目的就是为了找到结点i的根结点,直接看代码
public int find(int[] parent, int i) {if (parent[i] < 0) {//当前结点为根结点,终止return i;} else {return parent[i]; //返回父节点}}
这个时候我们来研究一下进行路径压缩的查找方式,我们需要合并0和4,如果我们只是粗暴的合并的话,这个时候0指向3,这个时候其实的查询长度就是越来越长,时间复杂度为O(n)

所以这个时候我们不进行路径压缩,压缩之后的图是这样的,这个时候时间复杂度大大降低,我们每次查找根结点的时候,只需要查找一次(递归一次)便可以找到根结点
进行路径压缩的查找方法要进行两个操作,最终目的肯定还是要找寻到根结点,但是其次他也进行了路径的压缩,例如下图一样,把本来的4指向3,修改成了4指向了2,其实可以精炼成一句话:找到根结点,并且把路径上所有节点的父亲结点修改为根结点.这样树的高度就变成了1(只含有一个结点(根结点)的高度为0).

public int find(int[] parent, int i) {if (parent[i] < 0) {//当前结点为根结点,终止return i;} else {parent[i] = find(parent, parent[i]); //父节点设为根结点return parent[i]; //返回父节点}}
3.合并
合并操作也可以简单粗暴的进行合成,我们只需要找到各自的祖先,任意的将一个合并到另一个上边即可,直接看代码
public void union(int[] parent, int i, int j) {int i_parent = find(parent, i);//寻找i的根结点int j_parent = find(parent, j);//寻找j的根结点parent[i_parent]=j_parent;//将根结点为i_parent的树合并到根结点j_parent上}
如果我们这个时候进行合并可能会出现这种情况,也就是一个高度较大的数合并到了一个高度较小的树上面,这个时候树的高度就会增加,如果我们是高度小的树合并到高度较大的树上边(两颗树的高度不相等),这个时候树的高度便不会增加,自然查找的时候会更加快速的查找到.

进行按秩(树的高度)进行合并
这种合并方式就是为了解决上面所提到的问题,合并是便会是这种情况,但是我们如果判断两棵树的高度大小呢,这个时候就可以解释以下初始化的为-1的目的了,一:如果当前结点的值为负数,可以判断当前结点为根结点,二.当前结点越小(也就是相反数越大),说明当前树的高度越大,我们只需要把更大的根结点的值合并到更小的根结点值上,便可以解决这个问题了.

这个时候存在一个特殊情况,也就是两棵树的高度一样高.这个时候无论如何进行合并,树的高度度会增加,因此这个时候我们可以把一个根结点合并到另一个根结点上,并把合并到的根结点的值减一

public void union(int[] parent, int i, int j) {int i_parent = find(parent, i);//寻找i的根结点int j_parent = find(parent, j);//寻找j的根结点if (i_parent != j_parent) {//此时的根结点相同,没有合并的必要if (parent[i_parent] < parent[j_parent]) {//此时表示i_parent的秩比j_parent的秩大parent[j_parent] = i_parent;//把j_parent合并到i_parent上} else {if (parent[i_parent] == parent[j_parent]) {parent[j_parent]--;}parent[i_parent] = j_parent;//把i_parent合并到j_parent上}}}
三.省份数量
1.题目描述
有
n个城市,其中一些彼此相连,另一些没有相连。如果城市a与城市b直接相连,且城市b与城市c直接相连,那么城市a与城市c间接相连。省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个
n x n的矩阵isConnected,其中isConnected[i][j] = 1表示第i个城市和第j个城市直接相连,而isConnected[i][j] = 0表示二者不直接相连。返回矩阵中 省份 的数量。
力扣:力扣
2.问题分析
这是一道很典型的并查集问题,题目的意思就是城市之间相连就是一个省份,把相连的城市合并为一颗树,最终就是寻找有多少个根结点(有多少颗树),也就是parent[i]有多少个是小于0的值,就是一棵树,最终返回数量就可以了
3.代码实现
public void init(int[] parent) {//初始值设置为-1for (int i = 0; i < parent.length; ++i) {parent[i] = -1;}}public int find(int[] parent, int i) {if (parent[i]<0) {return i;} else {parent[i] = find(parent, parent[i]); //父节点设为根结点return parent[i]; //返回父节点}}public void union(int[] parent, int i, int j) {int i_parent = find(parent, i);//寻找i的祖先int j_parent = find(parent, j);//寻找j的祖先if (i_parent != j_parent) {//此时的祖先相同,没有合并的必要if (parent[i_parent] < parent[j_parent]) {//此时表示i_parent的秩比j_parent的秩大parent[j_parent] = i_parent;//把j_parent合并到i_parent上} else {if (parent[i_parent] == parent[j_parent]) {parent[j_parent]--;}parent[i_parent] = j_parent;//把i_parent合并到j_parent上}}}public int findCircleNum(int[][] isConnected) {int[] parent = new int[isConnected.length];init(parent);for (int i = 0; i < isConnected.length; ++i) {for (int j = i + 1; j < isConnected[0].length; ++j) {if (isConnected[i][j] == 1) {union(parent, i, j);}}}int cnt = 0;for (int i = 0; i < parent.length; ++i) {if (parent[i] < 0)cnt++;}return cnt;}
四.冗余连接
1.题目描述
树可以看成是一个连通且 无环 的 无向 图。
给定往一棵
n个节点 (节点值1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在1到n中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为n的二维数组edges,edges[i] = [ai, bi]表示图中在ai和bi之间存在一条边。请找出一条可以删去的边,删除后可使得剩余部分是一个有着
n个节点的树。如果有多个答案,则返回数组edges中最后出现的边。
力扣:力扣
2.问题分析
题目的意思就是不能出现环,也就是如果两个两个点已经连通了,这个时候不能再在这两个点添加一条边,这个时候就冗余了,我们都知道一个连通分量有n个顶点,n-1条边,而这道题一共有n条边,我们需要寻找的就是这一条冗余的边,当find(parent,i,j)的根结点一样的时候,这个时候这两个顶点之间的边一定是冗余的,找到这样的一条边即可.
3.代码实现
public void init(int[] parent) {for (int i = 0; i < parent.length; ++i) {parent[i] = -1;}}public int find(int[] parent, int i) {if (parent[i] < 0)return i;else {parent[i] = find(parent, parent[i]);return parent[i];}}public void union(int[] parent, int i, int j) {int i_parent = find(parent, i);int j_parent = find(parent, j);if (i_parent != j_parent) {if (parent[i_parent] < parent[j_parent]) {parent[j_parent] = i_parent;} else {if (parent[i_parent] == parent[j_parent]) {parent[j_parent]--;}parent[i_parent] = j_parent;}}}public int[] findRedundantConnection(int[][] edges) {int[] parent = new int[edges.length+1];init(parent);for (int i = 0; i < edges.length; ++i) {if (find(parent, edges[i][0]) != find(parent, edges[i][1])) {union(parent, edges[i][0], edges[i][1]);} else {return edges[i];}}return null;}
相关文章:
并查集(不相交集)详解
目录 一.并查集 1.什么是并查集 2.并查集的基本操作 3.并查集的应用 4.力扣上的题目 二.三大操作 1.初始化 2.查找 3.合并 三.省份数量 1.题目描述 2.问题分析 3.代码实现 四.冗余连接 1.题目描述 2.问题分析 3.代码实现 一.并查集 1.什么是并查集 并查集&…...
10个最频繁用于解释机器学习模型的 Python 库
文章目录什么是XAI?可解释性实践的步骤技术交流1、SHAP2、LIME3、Eli54、Shapash5、Anchors6、BreakDown7、Interpret-Text8、aix360 (AI Explainability 360)9、OmniXAI10、XAI (eXplainable AI)XAI的目标是为模型的行为和决定提供有意义的解释,本文整理…...
final关键字:我偏不让你继承
哈喽,小伙伴们大家好,我是兔哥呀,今天就让我们继续这个JavaSE成神之路! 这一节啊,咱们要学习的内容是Java所有final关键字。 之前呢,我们学习了继承,这大大提高了代码的灵活性和复用性。但是总…...
8大主流编程语言的适用领域,你可能选错了语言
很多人学编程经常是脑子一热然后就去网上一搜资源就开始学习了,但学到了后面发现目前所学的东西并不是自己最喜欢的,好像自己更喜欢另一个技术,感觉自己学错了,于是乎又去学习别的东西。 结果竹篮打水一场空,前面所付…...
关于Python库的问题
关于Python库的问题 问题1: ModuleNotFoundError: No module named ‘requests’ Python库 Pycharm使用Requests库时报错: No module named requests’解决方法 未安装requests库,使用"pip install requests"命令安装 依然提示P…...
好记性不如烂笔头(2)
概述:用来记录一些小技巧。 1.查看MyBatis执行的sql 类:org.apache.ibatis.mapping.MappedStatement方法:getBoundSql(Object parameterObject)在IDEA的Evaluate Expression查看sql:boundSql.getSql() 2.maven仓库地址为https&…...
Java for循环嵌套for循环,你需要懂的代码性能优化技巧
前言 本篇分析的技巧点其实是比较常见的,但是最近的几次的代码评审还是发现有不少兄弟没注意到。 所以还是想拿出来说下。 正文 是个什么场景呢? 就是 for循环 里面还有 for循环, 然后做一些数据匹配、处理 这种场景。 我们结合实例代码来…...
关于我拒绝了腾讯测试开发岗offer这件事
2022年刚开始有了向要跳槽的想法,之前的公司不能算大厂但在重庆也算是数一数二。开始跳槽的的时候我其实挺犹豫的 其实说是有跳槽的想法在2022年过年的时候就有了,因为每年公司3月会有涨薪的机会,所以想着看看那能不能涨(其实还是…...
从GPT到GPT-3:自然语言处理领域的prompt方法
❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…...
Git代码提交规范
Git 代码规范Git 每次提交代码,都是需要写 Commit message(提交说明),否则就不允许提交。Commit message 的格式 (三部分):Heaher ----- 必填type ---必需scope --- 可选subject --- 必需Body ---- 可省略Footer ---- …...
【JavaScript速成之路】JavaScript内置对象--Math和Date对象
📃个人主页:「小杨」的csdn博客 🔥系列专栏:【JavaScript速成之路】 🐳希望大家多多支持🥰一起进步呀! 文章目录前言1,Math对象1.1,常用属性方法1.1.1,获取x的…...
(自用POC)Fortinet-CVE-2022-40684
本文转载于:https://mp.weixin.qq.com/s?__bizMzIzNDU5Mzk2OQ&mid2247485332&idx1&sn85931aa474f1ae2c23a66bf6486eec63&chksme8f54c4adf82c55c44bc7b1ea919d44d377e35a18c74f83a15e6e20ec6c7bc65965dbc70130d&mpshare1&scene23&srcid…...
ConvNeXt V2实战:使用ConvNeXt V2实现图像分类任务(二)
文章目录训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整算法设置混合精度,DP多卡,EMA定义训练和验证函数训练函数验证函数调用训练和验证方法运行以及结果查看测试热力图可视化展示完…...
【人工智能与深度学习】基于正则化潜在可变能量的模型
【人工智能与深度学习】基于正则化潜在可变能量的模型 正则化潜变量能量基础模型稀疏编码FISTALISTA稀疏编码示例卷积稀疏编码自然图像上的卷积稀疏编码可变自动编码器正则化潜变量能量基础模型 具有潜在变量的模型能够生成预测分布 y ‾ \overline{y}...
【Leetcode——排序的循环链表】
😊😊😊 文章目录一、力扣题之排序循环链表二、解题思路1. 使用双指针法2、找出最大节点,最大节点的下一个节点是最小节点,由此展开讨论总结一、力扣题之排序循环链表 题目如下:航班直达!&#…...
ChatGPT研究分享:机器第一次开始理解人类世界目录
0、为什么会对ChatGPT感兴趣一开始,我对ChatGPT是没什么关注的,无非就是有更大的数据集,完成了更大规模的计算,所以能够回答更多的问题。但后来了解到几个案例,开始觉得这个事情并不简单。我先分别列举出来,…...
【linux】Linux基本指令(上)
前言: 在之前我们已经简单了介绍了一下【Linux】,包括它的概念,由来啊等进行了讲解,接下来我们就将正式的踏入对其的学习!!! 本文目录👉操作系统的概念1.命令的语法1.1命令介绍1.2选…...
程序员必会技能—— 使用日志
目录 1、为什么要使用日志 2、自定义日志打印 2.1、在程序中得到日志对象 2.2、使用日志对象打印日志 2.3、日志格式 3、日志的级别 3.1、日志级别的分类 3.2、日志级别的设置 4、持久化日志 5、更简单的日志输出——lombok 5.1、如何在已经创建好的SpringBoot项目中添加…...
生成项目的包依赖文件requirements.txt
目录生成项目的包依赖文件requirements.txtrequirements.txt文件怎么来?使用pipreqs第三方库requirements.txt文件使用requirements.txt生成项目的包依赖文件requirements.txt 在安装部署代码时或者使用别人的项目时,会需要安装项目的依赖包,…...
安卓渐变的背景框实现
安卓渐变的背景框实现1.背景实现方法1.利用PorterDuffXfermode进行图层的混合,这是最推荐的方法,也是最有效的。2.利用canvas裁剪实现,这个方法有个缺陷,就是圆角会出现毛边,也就是锯齿。3.利用layer绘制边框1.背景 万…...
linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...
