图论之并查集——含例题

目录

介绍

秩是什么

例子——快速入门

例题

使用路径压缩,不使用秩合并

使用路径压缩和秩合并

无向图和有向图


介绍

并查集是一种用于 处理不相交集合的合并与查询问题的数据结构。它主要涉及以下基本概念和操作:
基本概念:
  • 集合:并查集中的集合是由一组元素组成的,这些元素具有相同的属性或特征,集合之间相互不相交。
  • 代表元素:每个集合都有一个代表元素,用于标识该集合。集合中的其他元素都可以通过一定的关系与代表元素相连。
基本操作:
  • 初始化:将每个元素都初始化为一个独立的集合,每个集合的代表元素就是该元素本身。
  • 合并:将两个不同集合合并为一个集合。通常是将一个集合的代表元素连接到另一个集合的代表元素上,使得两个集合成为一个更大的集合。
  • 查找:查找某个元素所在集合的代表元素。通过不断地沿着元素的父指针追溯,最终找到代表元素,从而确定该元素属于哪个集合。
并查集通常使用数组来实现,数组的下标表示元素,数组中存储的是该元素的父元素或代表元素的下标。在一些复杂的应用场景中,为了提高并查集的操作效率,还会采用路径压缩和按秩合并等优化策略。
并查集在 图论、数据分类、连通性问题等领域有广泛的应用。例如,在处理图的连通分量问题时,可以使用并查集来快速判断两个顶点是否属于同一个连通分量,以及合并不同的连通分量。

秩是什么

定义:秩可以看作是树的高度的一个估计值。在并查集的初始化阶段,每个元素都自成一个集合,此时集合的秩通常被初始化为 1,表示单个元素构成的树高度为 1。
作用: 在合并两个集合时,通过比较两个集合的秩来决定如何合并,以尽量保持树的平衡性,避免出现退化的树结构(即高度过高的树,会导致查找操作的时间复杂度增加)。
按秩合并策略:
  • 当合并两个集合时,比较它们的秩。如果两个集合的秩不同,将秩较小的集合合并到秩较大的集合中。这样做的原因是,将较小的树连接到较大的树上,对整体树的高度影响较小,有助于保持树的平衡性。例如,一个秩为 2 的树和一个秩为 3 的树合并,会将秩为 2 的树连接到秩为 3 的树下面,合并后新树的秩不变,仍为 3。
  • 如果两个集合的秩相同,那么可以任选一个集合作为合并的目标集合,并将另一个集合合并到该集合中。在这种情况下,合并后新集合的秩会增加 1。例如,两个秩都为 2 的树合并,合并后新树的秩变为 3。
通过使用秩和按秩合并策略,可以有效地降低并查集操作的时间复杂度,使得在大多数情况下,查找和合并操作都能在接近常数时间内完成。

例子——快速入门

假设有一群人,他们之间存在着不同的朋友关系。我们把每个人看作一个节点,朋友关系看作是连接节点的边,现在需要判断两个人是否在同一个朋友圈中,以及统计朋友圈的数量。
  • 初始化:假设有 5 个人,分别用编号 0 - 4 表示。一开始,每个人都属于自己独立的朋友圈,即每个节点的父节点都是它自己。可以用一个数组parent来表示,parent[i]表示节点i的父节点,初始化为parent = [0, 1, 2, 3, 4]。
  • 合并朋友圈:
    • 已知 0 和 1 是朋友,通过union操作合并他们所在的集合。找到 0 和 1 的根节点,即 0 和 1 本身,将 1 的父节点设置为 0,此时parent = [0, 0, 2, 3, 4],表示 0 和 1 在同一个朋友圈中。
    • 接着,2 和 3 是朋友,进行同样的合并操作,将 3 的父节点设置为 2,parent = [0, 0, 2, 2, 4]。
    • 然后,1 和 3 是朋友,再次合并。先找到 1 的根节点是 0,3 的根节点是 2,将 2 的父节点设置为 0,parent = [0, 0, 0, 0, 4],此时 0、1、2、3 都在同一个朋友圈中。
  • 查找:
    • 要判断 4 和 3 是否在同一个朋友圈,通过find操作查找 4 的根节点是 4,3 的根节点是 0,根节点不同,所以 4 和 3 不在同一个朋友圈。
    • 要判断 0 和 2 是否在同一个朋友圈,查找 0 和 2 的根节点都是 0,根节点相同,所以 0 和 2 在同一个朋友圈。
  • 统计朋友圈数量:最后,通过遍历parent数组,统计根节点的数量,即不同的代表元素的数量,就可以得到朋友圈的数量。在这个例子中,有两个不同的根节点 0 和 4,所以朋友圈数量为 2。
package mainimport "fmt"// UnionFind 定义并查集结构体
type UnionFind struct {parent []int // parent 切片用于存储每个元素的父节点,初始时每个元素的父节点是其自身// 在合并两个集合时,通过比较两个集合的秩来决定如何合并,以尽量保持树的平衡性,避免出现退化的树结构(即高度过高的树,会导致查找操作的时间复杂度增加)。rank  []int // rank 切片用于记录每个集合的秩(通常是树的高度)count int   // 朋友圈的数量
}// NewUnionFind 初始化并查集
func NewUnionFind(n int) *UnionFind {parent := make([]int, n)rank := make([]int, n)for i := range parent {parent[i] = irank[i] = 1}return &UnionFind{parent: parent,rank:   rank,count:  n,}
}// Find 查找元素所在集合的代表元素
func (uf *UnionFind) Find(x int) int {// 如果元素x的父节点(parent[x])不是它自己,就递归的查找它(parent[x]元素)的父节点if uf.parent[x] != x {uf.parent[x] = uf.Find(uf.parent[x])}return uf.parent[x]
}// Union 合并两个元素所在的集合
func (uf *UnionFind) Union(x, y int) {rootX := uf.Find(x)rootY := uf.Find(y)if rootX == rootY {return}if uf.rank[rootX] < uf.rank[rootY] {rootX, rootY = rootY, rootX}uf.parent[rootY] = rootX         // 更改 rootY 的父节点为 rootXuf.rank[rootX] += uf.rank[rootY] // 更改 rootX 的秩uf.count--                       // 朋友圈数量--
}// GetCount 获取连通分量的数量
func (uf *UnionFind) GetCount() int {return uf.count
}func main() {// 假设有 5 个人n := 5uf := NewUnionFind(n)// 合并操作,模拟朋友关系uf.Union(0, 1)uf.Union(2, 3)uf.Union(1, 3)// 判断 4 和 3 是否在同一个朋友圈sameCircle1 := uf.Find(4) == uf.Find(3)fmt.Printf("4 和 3 是否在同一个朋友圈: %v\n", sameCircle1)// 判断 0 和 2 是否在同一个朋友圈sameCircle2 := uf.Find(0) == uf.Find(2)fmt.Printf("0 和 2 是否在同一个朋友圈: %v\n", sameCircle2)// 统计朋友圈的数量circleCount := uf.GetCount()fmt.Printf("朋友圈的数量: %d\n", circleCount)// 4 和 3 是否在同一个朋友圈: false// 0 和 2 是否在同一个朋友圈: true// 朋友圈的数量: 2
}

例题

在并查集的实现中,rank 数组(或类似用于记录秩的机制)并不是必需的,有些题目里的并查集没有使用 rank 数组主要有以下原因:

简化实现:对于一些简单的问题场景,不需要通过按秩合并来优化并查集的性能,仅使用路径压缩就可以满足时间复杂度要求。此时可以省略 rank 数组,代码实现会更简洁。比如在一些数据规模较小或者对时间复杂度要求不高的问题中,单纯的路径压缩就能让并查集的操作效率足够高。

采用其他优化方式:有些并查集的实现可能不使用 rank 数组来记录秩,而是采用其他方式来优化合并操作。例如,记录每个集合的大小,在合并时将较小的集合合并到较大的集合中,这种方法也能在一定程度上避免树结构的退化,提高查找和合并的效率。

使用路径压缩,不使用秩合并

// 使用路径压缩,不使用秩合并package maintype UnionFind struct {parent []int
}func NewUnionFind(n int) *UnionFind {parent := make([]int, n)for i := range parent {parent[i] = i}return &UnionFind{parent: parent,}
}// Find 查找
func (uf *UnionFind) Find(x int) int {if uf.parent[x] != x {uf.parent[x] = uf.Find(uf.parent[x])}return uf.parent[x]
}// Union 合并
func (uf *UnionFind) Union(x, y int) {rootX := uf.Find(x)rootY := uf.Find(y)uf.parent[rootY] = rootX
}// IsConnected 判断两个元素是否在同一个集合中
func (uf *UnionFind) IsConnected(x, y int) bool {return uf.Find(x) == uf.Find(y)
}

相应的例题:

力扣:547. 省份数量(并查集,也可以用dfs、bfs)

力扣:684. 冗余连接(并查集)

使用路径压缩和秩合并

// 使用路径压缩和秩合并(优化并查集的性能)package main// UnionFind 定义并查集结构体
type UnionFind struct {parent []intrank   []int
}// NewUnionFind 初始化并查集
func NewUnionFind(n int) *UnionFind {parent := make([]int, n)rank := make([]int, n)for i := range parent {parent[i] = irank[i] = 1}return &UnionFind{parent: parent,rank:   rank,}
}// Find 查找元素所在集合的代表元素,使用路径压缩
func (uf *UnionFind) Find(x int) int {if uf.parent[x] != x {uf.parent[x] = uf.Find(uf.parent[x])}return uf.parent[x]
}// Union 合并两个元素所在的集合,使用按秩合并
func (uf *UnionFind) Union(x, y int) {rootX := uf.Find(x)rootY := uf.Find(y)if rootX == rootY {return}if uf.rank[rootX] < uf.rank[rootY] {rootX, rootY = rootY, rootX}uf.parent[rootY] = rootXuf.rank[rootX] += uf.rank[rootY]
}// IsConnected 判断两个元素是否在同一个集合中
func (uf *UnionFind) IsConnected(x, y int) bool {return uf.Find(x) == uf.Find(y)
}

相应的例题:

力扣:1584. 连接所有点的最小费用(Kruskal算法、最小生成树、并查集)

无向图和有向图

并查集在无向图中的应用更为直接和常见。(当然,在一些有向图的问题中也能通过适当的转化和处理来发挥作用)

相应的例题:

力扣:2101. 引爆最多的炸弹(有向图)

问:这道题为什么不能用并查集?

答:注意本题是有向图。例如炸弹 0 可以引爆炸弹 2,炸弹 1 可以引爆炸弹 2,对应有向边 0→2,1→2,那么正确答案是 2。如果用并查集做的话,会把 0,1,2 三个点合并起来,计算出错误的答案 3。

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

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

相关文章

【数学建模】(智能优化算法)天牛须算法(Beetle Antennae Search, BAS)详解与Python实现

天牛须算法(Beetle Antennae Search, BAS)详解与Python实现 文章目录 天牛须算法(Beetle Antennae Search, BAS)详解与Python实现1. 引言2. 算法原理2.1 基本思想2.2 数学模型 3. Python实现4.实测效果测试1. Michalewicz函数的最小化测试2. Goldstein-Price函数的约束最小化 5…

【家政平台开发(42)】筑牢家政平台安全防线:安全测试与漏洞修复指南

本【家政平台开发】专栏聚焦家政平台从 0 到 1 的全流程打造。从前期需求分析,剖析家政行业现状、挖掘用户需求与梳理功能要点,到系统设计阶段的架构选型、数据库构建,再到开发阶段各模块逐一实现。涵盖移动与 PC 端设计、接口开发及性能优化,测试阶段多维度保障平台质量,…

学习笔记八——内存管理相关

&#x1f4d8; 目录 内存结构基础&#xff1a;栈、堆、数据段Rust 的内存管理机制&#xff08;对比 C/C、Java&#xff09;Drop&#xff1a;Rust 的自动清理机制Deref&#xff1a;为什么 *x 能访问结构体内部值Rc&#xff1a;多个变量“共享一个资源”怎么办&#xff1f;Weak&…

ReliefF 的原理

&#x1f31f; ReliefF 是什么&#xff1f; ReliefF 是一种“基于邻居差异”的特征选择方法&#xff0c;用来评估每个特征对分类任务的贡献大小。 它的核心问题是&#xff1a; “我怎么知道某个特征是不是重要&#xff1f;是不是有能力把不同类别的数据区分开&#xff1f;” 而…

​asm汇编源代码之-汉字点阵字库显示程序源代码下载​

汉字点阵字库显示程序 源代码下载 文本模式下显示16x16点阵汉字库内容的程序(标准16x16字库需要使用CHGHZK转换过后才能使用本程序正常显示) 本程序需要调用file.asm和string.asm中的子程序,所以连接时需要把它们连接进来,如下 C:\> tlink showhzk file string 调用参…

【已更新完毕】2025泰迪杯数据挖掘竞赛B题数学建模思路代码文章教学:基于穿戴装备的身体活动监测

基于穿戴装备的身体活动监测 摘要 本研究基于加速度计采集的活动数据&#xff0c;旨在分析和统计100名志愿者在不同身体活动类别下的时长分布。通过对加速度数据的处理&#xff0c;活动被划分为睡眠、静态活动、低强度、中等强度和高强度五类&#xff0c;进而计算每个志愿者在…

Ubuntu24.04装机安装指南

文章目录 Ubuntu24.04装机安装指南一、分区说明二、基础软件三、使用fcitx5配置中文输入法四、安装搜狗输入法【**不推荐**】1. 安装fcitx2. 安装输入法 五、禁用/home目录下自动生成文件夹六、更新软件源1. 针对**新配置方式**的清华源替换方法2. 针对**老配置方式**的清华源替…

互联网三高-数据库高并发之分库分表ShardingJDBC

1 ShardingJDBC介绍 1.1 常见概念术语 ① 数据节点Node&#xff1a;数据分片的最小单元&#xff0c;由数据源名称和数据表组成 如&#xff1a;ds0.product_order_0 ② 真实表&#xff1a;再分片的数据库中真实存在的物理表 如&#xff1a;product_order_0 ③ 逻辑表&#xff1a…

BM25、BGE以及text2vec-base-chinese的区别

BM25、BGE以及text2vec-base-chinese的区别 BM25 原理:BM25(Best Matching 25)是一种基于概率检索模型的算法,它通过考虑查询词与文档之间的匹配程度、文档的长度等因素,来计算文档对于查询的相关性得分。具体来说,它会给包含查询词次数较多、文档长度适中的文档更高的分…

Python中try用法、内置异常类型与自定义异常类型拓展

目录 try介绍与语法格式try具体使用案例except的异常类型简介案例内置的常见异常类型自定义异常类型继承关系用途 注意事项 try介绍与语法格式 在 Python 里&#xff0c;try 语句主要用于异常处理&#xff0c;其作用是捕获并处理代码运行期间可能出现的异常&#xff0c;避免程…

【第41节】windows的中断与异常及异常处理方式

目录 一、中断与异常处理 1.1 中断与异常 1.2 IDT 1.3 异常的概念 1.4 异常分类 二、windows异常处理方式 2.1 概述 2.2 结构化异常处理 2.3 向量化异常处理之VEH 2.4 向量化异常处理之VCH 2.5 默认的异常处理函数 2.6 如何手动安装 SEH 节点 2.7 异常处理的优先级…

分布式日志治理:Log4j2自定义Appender写日志到RocketMQ

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

【HTML】html文件

HTML文件全解析&#xff1a;搭建网页的基石 在互联网的广袤世界里&#xff0c;每一个绚丽多彩、功能各异的网页背后&#xff0c;都离不开HTML文件的默默支撑。HTML&#xff0c;即超文本标记语言&#xff08;HyperText Markup Language&#xff09;&#xff0c;作为网页创建的基…

oracle命令上下左右键无法使用如何解决?

1、问题如图 2、解决办法 (1) 安装readline yum -y install readline* &#xff08;2&#xff09;安装 rlwrap ##下载 wget http://files.cnblogs.com/files/killkill/rlwrap-0.30.tar.gz.zip ##解压 tar -xzvf rlwrap-0.30.tar.gz.zip ##编译安装 ./configure make &&…

vue事假机制都有哪些

Vue 的事件机制主要包含以下几种类型和方式&#xff0c;可以分为组件内部事件、父子组件通信事件、原生 DOM 事件封装、修饰符增强等&#xff0c;下面详细分类介绍&#xff1a; 一、DOM 事件绑定&#xff08;最基础的事件&#xff09; 使用 v-on&#xff08;或简写 &#xff0…

系统编程2(消息队列)

⦁ 消息队列概念 Linux系统中消息队列&#xff08;Message Queue&#xff09;是进程间通信的一种方式&#xff0c;这种通信机制的好处是可以传输指定类型(用户可以自行定义)的数据&#xff0c;相同类型的数据根据到达顺序在队列中进行排队。 当然&#xff0c;不同类型的数据不…

Pytorch深度学习框架60天进阶学习计划 - 第41天:生成对抗网络进阶(二)

Pytorch深度学习框架60天进阶学习计划 - 第41天&#xff1a;生成对抗网络进阶&#xff08;二&#xff09; 7. 实现条件WGAN-GP # 训练条件WGAN-GP def train_conditional_wgan_gp():# 用于记录损失d_losses []g_losses []# 用于记录生成样本的多样性&#xff08;通过类别分…

python 微博爬虫 01

起因&#xff0c; 目的: ✅下载单个视频&#xff0c;完成。✅ 获取某用户的视频列表&#xff0c;完成。剩下的就是&#xff0c; 根据视频列表&#xff0c;逐个下载视频&#xff0c;我没做&#xff0c;没意思。获取视频的评论&#xff0c;以后再说。 关键点记录: 1. 对一个视…

Servlet、HTTP与Spring Boot Web全面解析与整合指南

目录 第一部分&#xff1a;HTTP协议与Servlet基础 1. HTTP协议核心知识 2. Servlet核心机制 第二部分&#xff1a;Spring Boot Web深度整合 1. Spring Boot Web架构 2. 创建Spring Boot Web应用 3. 控制器开发实践 4. 请求与响应处理 第三部分&#xff1a;高级特性与最…

vue中根据html动态渲染内容2.0

上次使用的是p标签用的contenteditable代替的可编辑的input&#xff0c;最后实现还是选择了用el-input的textarea方式。 一开始考虑的是需要根据用户输入自动撑开输入框&#xff0c;所以选择了p标签可编辑。 最后发现还是el-input会更好一点&#xff0c;只不过需要处理输入框撑…