数据结构与算法----复习Part 16 (并查集)

本系列是算法通关手册LeeCode的学习笔记

算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn)

 

目录

并查集(Union Find)

基于数组实现的快速查询并查集

基于森林实现的快速合并并查集

路径压缩

隔代压缩

完全压缩

按深度合并

按大小合并

例题


并查集(Union Find)

        一种树型的数据结构,用于处理一系列灭有重复元素集合的合并及查询问题。

        合并(Union):将两个元素合并成一个集合;

        查找(Find):确定某个元素属于哪个集合。通常返回集合内的某个【代表元素】。

        并查集的主要任务有:

                合并 union(x, y):将集合 x 和集合 y 合并成一个集合;

                查找 find(x):查找元素 x 属于哪个集合;

                查找 is_connected(x, y):查询元素 x 和元素 y 是否在同一个集合中。

        出于【快速查询】和【快速合并】两种目的,可以有两种实现方式。

基于数组实现的快速查询并查集

        如果希望并查集的查询效率高一些,可以使用一个数组来表示集合中的元素,数组元素和集合元素是一一对应的,可以将数组的索引值作为每个元素的集合编号,称为 id。

        初始化:将每个元素的集合编号初始化为数组下标索引。则所有元素的 id 都是唯一的,代表着每个元素单独属于一个集合;

        合并:将其中一个集合中的所有元素 id 更改为另一个集合中的 id;

        查找:如果两个元素的 id 一样,则说明它们属于同一个集合,否则不属于同一集合。

        初始化后,每个元素属于各自的集合,即有 0 到 7 这 8 个集合,每个集合有 1 个元素

        合并后,为 { 0 }, { 1, 2, 3 }, { 4 }, { 5, 6 }, { 7 } 这 5 个集合。

class UnionFindArray:def __init__(self, n):self.ids = [i for i in range(n)]def find(self, x):return self.ids[x]def union(self, x, y):x_id = self.find(x)y_id = self.find(y)if x_id == y_id:returnfor i in range(len(self.ids)):if self.ids[i] == y_id:     # 找到要合并的数组self.ids[i] = x_id      # 完成合并,合并后的数组标号为 x_idreturndef is_connected(self, x, y):return self.find(x) == self.find(y)

        单次查询操作的时间复杂度是 O(1);

        单次合并操作的时间复杂度为 O(n)

基于森林实现的快速合并并查集

        为提高合并操作的效率,我们可以使用一个【森林】来存储所有集合。每一棵树代表一个集合,树上的每个节点都是一个元素,树根节点为这个集合的代表元素。

PS:与普通树形结构不同的是,基于森林实现的并查集中,树中的子节点是指向父节点的。

        我们使用一个数组 fa 来记录这个森林。我们用 fa[x] 来保存 x 的父节点的集合编号,代表着 x 指向父节点 fa[x]。

        初始化:将每个元素的集合编号初始化为数组 fa 的下标索引。所有元素的根节点的集合编号不一样,代表着每个元素单独属于一个集合;

        合并操作:需要将两个集合的树根节点相连接,令其中一个集合的树根节点指向另一个集合的树根节点;

        查找操作:分别从两个元素开始,通过数组 fa 存储的值,不断访问父节点,直到达到树根节点。判断两个元素的树根节点是否一样。

        进行合并操作:

                union(4,5)、union(6,7)、union(4,7)

                    fa[4] = fa[5] = fa[6] = fa[fa[7]]

结果为 { 0 },{ 1 },{ 2 },{ 3 },{ 4,5,6,7 }

class UnionFind:def __init__(self, n):self.fa = [i for i in range(n)]def find(self, x):while self.fa[x] != x:x = self.fa[x]return xdef union(self, x, y):root_x = self.find(x)root_y = self.find(y)if root_x == root_y:returnself.fa[root_x] = root_yreturndef is_connected(self, x, y):return self.find(x) == self.find(y)

路径压缩

        在集合很大或者树很不平衡时,使用【快速合并】实现并查集的代码效率很差

        单词查询的复杂度会达到 O(n)。

        为避免上图情况,可以使用路径压缩:

                在从底向上查找根节点的过程中,如果此时访问的不是根节点,则可以把这个节点尽量

        向上移动以下,从而减少树的层数,这个过程叫做路径压缩,又分为隔代压缩和完全压缩。

隔代压缩

        在查询时,两步一压缩,循环执行【把当前节点指向它父节点的父节点】这一操作,从而减少树的深度。

    def throwBackFind(self, x):while self.fa[x] != x:self.fa[x] = self.fa[self.fa[x]]x = self.fa[x]return x
完全压缩

        在查询时,把被查询的节点到根节点的路径上的所有节点的父节点设置为根节点,从而减小树的深度。

    def completeFind(self, x):if self.fa[x] != x:self.fa[x] = self.completeFind(self.fa[x])return self.fa[x]

        后续还有【按深度合并】与【按大小合并】,这里给出图解与代码,思路与上述大致相同,具体的细节可以看代码中的注释。

按深度合并

class UnionFind:def __init__(self, n):                          # 初始化self.fa = [i for i in range(n)]             # 每个元素的集合编号初始化为数组 fa 的下标索引self.rank = [1 for i in range(n)]           # 每个元素的深度初始化为 1def find(self, x):                              # 查找元素根节点的集合编号内部实现方法while self.fa[x] != x:                      # 递归查找元素的父节点,直到根节点self.fa[x] = self.fa[self.fa[x]]        # 隔代压缩x = self.fa[x]return x                                    # 返回元素根节点的集合编号def union(self, x, y):                          # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点root_x = self.find(x)root_y = self.find(y)if root_x == root_y:                        # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合return Falseif self.rank[root_x] < self.rank[root_y]:   # x 的根节点对应的树的深度 小于 y 的根节点对应的树的深度self.fa[root_x] = root_y                # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点elif self.rank[root_y] > self.rank[root_y]: # x 的根节点对应的树的深度 大于 y 的根节点对应的树的深度self.fa[root_y] = root_x                # y 的根节点连接到 x 的根节点上,成为 x 的根节点的子节点else:                                       # x 的根节点对应的树的深度 等于 y 的根节点对应的树的深度self.fa[root_x] = root_y                # 向任意一方合并即可self.rank[root_y] += 1                  # 因为层数相同,被合并的树必然层数会 +1return Truedef is_connected(self, x, y):                   # 查询操作:判断 x 和 y 是否同属于一个集合return self.find(x) == self.find(y)

按大小合并

class UnionFind:def __init__(self, n):                          # 初始化self.fa = [i for i in range(n)]             # 每个元素的集合编号初始化为数组 fa 的下标索引self.size = [1 for i in range(n)]           # 每个元素的集合个数初始化为 1def find(self, x):                              # 查找元素根节点的集合编号内部实现方法while self.fa[x] != x:                      # 递归查找元素的父节点,直到根节点self.fa[x] = self.fa[self.fa[x]]        # 隔代压缩优化x = self.fa[x]return x                                    # 返回元素根节点的集合编号def union(self, x, y):                          # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点root_x = self.find(x)root_y = self.find(y)if root_x == root_y:                        # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合return Falseif self.size[root_x] < self.size[root_y]:   # x 对应的集合元素个数 小于 y 对应的集合元素个数self.fa[root_x] = root_y                # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点self.size[root_y] += self.size[root_x]  # y 的根节点对应的集合元素个数 累加上 x 的根节点对应的集合元素个数elif self.size[root_x] > self.size[root_y]: # x 对应的集合元素个数 大于 y 对应的集合元素个数self.fa[root_y] = root_x                # y 的根节点连接到 x 的根节点上,成为 x 的根节点的子节点self.size[root_x] += self.size[root_y]  # x 的根节点对应的集合元素个数 累加上 y 的根节点对应的集合元素个数else:                                       # x 对应的集合元素个数 小于 y 对应的集合元素个数self.fa[root_x] = root_y                # 向任意一方合并即可self.size[root_y] += self.size[root_x]return Truedef is_connected(self, x, y):                   # 查询操作:判断 x 和 y 是否同属于一个集合return self.find(x) == self.find(y)

例题

990. 等式方程的可满足性

class UnionFind:def __init__(self, n):                          # 初始化self.fa = [i for i in range(n)]             # 每个元素的集合编号初始化为数组 fa 的下标索引def __find(self, x):                            # 查找元素根节点的集合编号内部实现方法while self.fa[x] != x:                      # 递归查找元素的父节点,直到根节点self.fa[x] = self.fa[self.fa[x]]        # 隔代压缩优化x = self.fa[x]return x                                    # 返回元素根节点的集合编号def union(self, x, y):                          # 合并操作:令其中一个集合的树根节点指向另一个集合的树根节点root_x = self.__find(x)root_y = self.__find(y)if root_x == root_y:                        # x 和 y 的根节点集合编号相同,说明 x 和 y 已经同属于一个集合return Falseself.fa[root_x] = root_y                    # x 的根节点连接到 y 的根节点上,成为 y 的根节点的子节点return Truedef is_connected(self, x, y):                   # 查询操作:判断 x 和 y 是否同属于一个集合return self.__find(x) == self.__find(y)class Solution:def equationsPossible(self, equations: List[str]) -> bool:union_find = UnionFind(26)for eqation in equations:if eqation[1] == "=":index1 = ord(eqation[0]) - 97index2 = ord(eqation[3]) - 97union_find.union(index1, index2)for eqation in equations:if eqation[1] == "!":index1 = ord(eqation[0]) - 97index2 = ord(eqation[3]) - 97if union_find.is_connected(index1, index2):return Falsereturn True

547. 省份数量

class UnionFind:def __init__(self, n):self.fa = [i for i in range(n)]def find(self, x):if self.fa[x] != x:self.fa[x] = self.find(self.fa[x])return self.fa[x]def union(self, x, y):root_x = self.find(x)root_y = self.find(y)if root_x == root_y:returnself.fa[root_x] = root_yreturndef is_connected(self, x, y):return self.find(x) == self.find(y)class Solution:def findCircleNum(self, isConnected: List[List[int]]) -> int:n = len(isConnected)uf = UnionFind(n)for i in range(n):for j in range(i + 1, n):if isConnected[i][j] == 1:uf.union(i, j)res = set()for i in range(n):res.add(uf.find(i))return len(res)

1319. 连通网络的操作次数

class UnionFind:def __init__(self, n):self.fa = [i for i in range(n)]self.size = [1 for i in range(n)]def find(self, x):if self.fa[x] != x:self.fa[x] = self.find(self.fa[x])return self.fa[x]def union(self, x, y):root_x = self.find(x)root_y = self.find(y)if root_x == root_y:returnself.fa[root_x] = root_yself.size[root_y] += self.size[root_x]returndef is_connected(self, x, y):return self.find(x) == self.find(y)class Solution:def makeConnected(self, n: int, connections: List[List[int]]) -> int:if len(connections) < n - 1 :return -1uf = UnionFind(n)res = 0for num in connections:if uf.find(num[0]) == uf.find(num[1]):continueuf.union(num[0], num[1])for i in range(1, n):if uf.is_connected(i - 1, i):continueuf.union(i - 1, i)res += 1return res

算法通关手册(LeetCode) | 算法通关手册(LeetCode)

原文内容在这里,如有侵权,请联系我删除。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

51单片机-AT24C02(I2C总线)

目录 一&#xff0c;介绍及元件工作原理 7.时序结构&#xff08;重要&#xff09; 8.i2C总线数据帧&#xff08;重要&#xff09; 二&#xff0c;应用 一&#xff0c;介绍及元件工作原理 1.元件介绍 2.存储器 3.地址总线和数据总线 地址总线只能一次选中一行 4.引脚及应用…

三次握手seq和ack的流程 TCP协议栈seq和ack深层理解

☆ 大家可以把想了解的问题在评论发给我?我会根据问题补充到后面 ☆ 三次握手seq和ack的流程 是的,在TCP/IP协议中,三次握手过程确实涉及到序列号(Sequence Number, 简称Seq)和确认号(Acknowledgment Number, 简称Ack)的交换。这个过程是为了建立可靠的连接,确保数据能…

Python基础(七)之数值类型字典

Python基础&#xff08;七&#xff09;之数值类型字典 1、简介 字典&#xff08;dict&#xff09;&#xff0c;Dictionary:是一种可变类型&#xff0c;可以存储任意类型的元素&#xff0c;如列表、字符串、元组等。 字典是无序的&#xff0c;所以不支持索引和切片。字典以键值…

CSS中如何设置单行或多行内容超出后,显示省略号

1. 设置超出显示省略号 css设置超出显示省略号可分两种情况&#xff1a; 单行文本溢出显示省略号…多行文本溢出显示省略号… 但使用的核心代码是一样的&#xff1a;需要先使用 overflow:hidden;来把超出的部分隐藏&#xff0c;然后使用text-overflow:ellipsis;当文本超出时…

软考 系统架构设计师之回归及知识点回顾(7)

接前一篇文章&#xff1a;软考 系统架构设计师之回归及知识点回顾&#xff08;6&#xff09; 11. 云计算 背景 大数据和云计算已成为IT领域的两种主流技术。“数据是重要资产”这一概念已成为大家的共识&#xff0c;众多公司争相分析、挖掘大数据背后的重要财富。同时学术界、…

哔哩哔哩后端Java一面

前言 作者&#xff1a;晓宜 个人简介&#xff1a;互联网大厂Java准入职&#xff0c;阿里云专家博主&#xff0c;csdn后端优质创作者&#xff0c;算法爱好者 最近各大公司的春招和实习招聘都开始了&#xff0c;这里分享下去年面试B站的的一些问题&#xff0c;希望对大家有所帮助…

第十二届蓝桥杯EDA省赛真题分析

前言&#xff1a; 第十二届蓝桥杯EDA比赛用的是AD软件&#xff0c;从第十四届起都是使用嘉立创EDA专业版&#xff0c;所以在这里我用嘉立创EDA专业版实现题目要求。 一、省赛第一套真题题目 主观题整套题目如下&#xff1a; 试题一&#xff1a;库文件设计&#xff08;5分&am…

VS Code 配置类似浏览器中的垂直标签页功能

参考&#xff1a;Dominik Weber - 2022.06.25 (注&#xff1a;原文中的配置有些过时了&#xff0c;所以根据 VS Code 的最新版本进行了调整。) 原作者非常喜欢垂直标签页&#xff0c;只要有可能&#xff0c;就都会使用它们。他主要在浏览器&#xff08;Firefox&#xff09;和…

Python之Web开发中级教程----ubuntu中下载安装Postman

Python之Web开发中级教程----ubuntu中下载安装Postman PostMan 是一款功能强大的网页调试与发送网页 HTTP 请求的 Chrome 插件&#xff0c;可以直接去对我们写出来的路由和视图函数进行调试&#xff0c;作为后端程序员是必须要知道的一个工具。 查看ubuntu系统中是否已经安装了…

VsCode 配置go开发环境之下载go tools

ctrl shift P 选择 go install/update tools&#xff0c;下载go tools 报错&#xff0c; 提升dial err。 将GOPROXY 和 GOSUMDB 按照如下配置&#xff0c;重启IDE即可成功下载 set GOPROXYhttps://goproxy.cn set GOSUMDBoff

程序人生——Java多线程和并发的使用建议

目录 引出多线程和并发建议118&#xff1a;不推荐覆写start方法建议119&#xff1a;启动线程前stop方法是不可靠的建议120&#xff1a;不适用stop方法停止线程 建议121&#xff1a;线程优先级只使用三个等级建议122&#xff1a;使用线程异常处理器提升系统可靠性建议123&#x…

【递归专题】【蓝桥杯备考训练】:有序分数、正则问题、带分数、约数之和、分形之城【已更新完成】

目录 1、有序分数&#xff08;usaco training 2.1&#xff09; 2、正则问题&#xff08;第八届蓝桥杯省赛C A组 & Java A组&#xff09; 3、带分数&#xff08;第四届蓝桥杯省赛Java A组/B组 & C B组/C组&#xff09; 4、约数之和&#xff08;《算法竞赛进阶指南》…

jvm的垃圾回收器以及触发full gc的场景

JVM&#xff08;Java虚拟机&#xff09;的垃圾回收器有很多种&#xff0c;主要包括以下几种&#xff1a; Serial收集器&#xff1a;串行收集器是最古老、最稳定的收集器。它使用单个线程进行垃圾收集工作&#xff0c;在进行垃圾回收时会暂停所有用户线程。 ParNew收集器&#…

ViT如何支持变长序列输入?

当增加输入图像的分辨率时&#xff0c;例如DeiT 从 224 到 384&#xff0c;一般来说会保持 patch size&#xff08;例如9&#xff09;&#xff0c;因此 patch 的数量 N 会发生了变化。那么视觉transformer是如何处理变长序列输入的? DEiT中如何处理mask数据的&#xff1f; 例…

智慧公厕对于智慧城市管理的意义

近年来&#xff0c;智慧城市的概念不断被提及&#xff0c;而智慧公厕作为智慧城市管理的重要组成部分&#xff0c;其在监测、管理和养护方面发挥着重要的作用。智慧公厕不仅是城市市容提升的重要保障&#xff0c;还能提升城市环境卫生管理的质量&#xff0c;并有效助力创造清洁…

5_相机标定2_calibrateCamera()与内外参

彩色角点图片镇楼 opencv官方文档&#xff1a; https://docs.opencv.org/4.8.0/d4/d94/tutorial_camera_calibration.html https://docs.opencv.org/3.4.18/d9/d0c/group__calib3d.html#gaebfc1c9f7434196a374c382abf43439b 相机标定目的&#xff1a; cv::calibrateCamera()的…

Arthas使用案例(二)

说明&#xff1a;记录一次使用Arthas排查测试环境正在运行的项目BUG&#xff1b; 场景 有一个定时任务&#xff0c;该定时任务是定时去拉取某FTP服务器上的文件&#xff0c;进行备份、读取、解析等一系列操作。 而现在&#xff0c;因为开发环境是Windows&#xff0c; 线上项…

SpringBoot(数据库操作 + druid监控功能)

文章目录 1.JDBC HikariDataSource&#xff08;SpringBoot2默认数据源&#xff09;1.数据库表设计2.引入依赖 pom.xml3.配置数据源参数 application.yml4.编写一个bean&#xff0c;映射表5.编写测试类来完成测试1.引入依赖 pom.xml2.使用JdbcTemplate进行测试3.成功&#xff0…

将OpenCV与gcc和CMake结合使用

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV4.9.0开源计算机视觉库在 Linux 中安装 下一篇&#xff1a; 引言&#xff1a; 近年来&#xff0c;计算机视觉技术在图像处理、目标检测和机器人等方面得到了广泛的应用…

YOLOv9改进策略:注意力机制 | 归一化的注意力模块(NAM)

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a; NAM作为一种高效且轻量级的注意力机制。采用了CBAM的模块集成并重新设计了通道和空间注意子模块。 yolov9-c-NAMAttention summary: 965 layers, 51000614 parameters, 51000582 gradients, 238.9 GFLOPs 改…