数据结构 - 并查集 Union Find

news/2025/2/24 15:19:10/文章来源:https://www.cnblogs.com/wenbinteng/p/18734138

在图论问题中,判断两个节点是否属于同一连通分量是一个高频任务,例如:

  • 判断网络中两台计算机是否能够通信;
  • 合并不同社交圈以形成更大的群组;
  • 处理动态连通性问题,比如动态增加边、节点的图。

如果直接通过图的遍历(如 BFS 或 DFS)来判断连通性,效率可能不够理想,特别是在需要频繁查询或修改的情况下。这时,我们可以使用一种高效的数据结构——并查集(Union-Find)

并查集以其简洁性和高效性成为解决动态连通性问题的强大工具。本文将详细介绍并查集的核心概念、操作方法、优化技巧以及实际应用场景。

1. 什么是并查集?

并查集是一种树形结构的数据结构,用于处理元素的分组连通性判断问题。其核心思想是:

  • 每个节点属于一个集合,用一棵树表示集合;
  • 每个集合的树有一个“根节点”作为代表;
  • 通过记录树的根节点,我们可以快速判断两个节点是否属于同一集合。

2. 并查集的两大核心操作

初始化:

int parent[MAX_N];
void init(int n) {for (int i = 1; i <= n; i++)parent[i] = i;
}

2.1 查找(Find)

查找某个元素所属的集合代表(即根节点)。

  • 如果两个元素的根节点相同,则它们属于同一个集合。
int find(int x) {if (parent[x] == x)return x;return find(parent[x]);
}

2.2 合并(Union)

将两个元素所属的集合合并成一个集合。(为了避免使用 C/C++ 中的关键字 union,这里使用 merge 作为操作名称)

  • 找到两个元素的根节点,然后将其中一个根节点挂到另一个根节点上。
void merge(int x, int y) {int xRoot = find(x);int yRoot = find(y);if (xRoot != yRoot)parent[xRoot] = yRoot;
}

3. 并查集的优化方法

3.1 路径压缩

通过上述方法构造的并查集是比较低效的:如果在根节点前部或者在叶节点后部不断插入新的节点,则会形成一条长链,使得查找操作的用时变长。我们可以使用路径压缩方法降低这种影响。

如果我们只关心每个节点的根节点,那么我们在查询过程中,把沿途每个节点的父节点都设置为根节点即可。

int find(int x) {if (parent[x] != x)parent[x] = find(parent[x]);return parent[x];
}

3.2 按秩合并

路径压缩能改善查找分支上的路径长度,但树型比较复杂时,合并操作可能会带来较长的路径。一个简单的启发式思想是,应该把层数较少的树添加到层数较深的树上,以避免更长的查找路径。

我们用一个数组 rank[] 来记录每个根节点对应的树的深度(秩)。初始时所有元素的 rank 值为 1。合并时比较两个根节点的 rank 值,把 rank 值较大的根节点作为 rank 值较小的根节点的前驱。

void merge(int x, int y) {int xRoot = find(x);int yRoot = find(y);if (xRoot != yRoot) {if (rank[xRoot] > rank[yRoot])parent[yRoot] = xRoot;else {parent[xRoot] = yRoot;if (rank[xRoot] == rank[yRoot])rank[yRoot]++;}}
}

4. 并查集的复杂度分析

  • 时间复杂度:同时使用路径压缩和启发式合并之后,并查集的每个操作平均时间为 \(O(\alpha(n))\),其中 \(\alpha(n)\) 为阿克曼函数的反函数,其增长极其缓慢,也就是说其单次操作的平均运行时间可以认为是一个很小的常数。
  • 空间复杂度:\(O(n)\)

5. 并查集的模板

class UnionFind {
public:void init(int n) {parent = std::vector<int>(n);rank = std::vector<int>(n, 1);for (int i = 0; i < n; i++) {parent[i] = i;}}int find(int x) {if (parent[x] != x)parent[x] = find(parent[x]);return parent[x];}void merge(int x, int y) {int xRoot = find(x);int yRoot = find(y);if (xRoot != yRoot) {if (rank[xRoot] > rank[yRoot])parent[yRoot] = xRoot;else {parent[xRoot] = yRoot;if (rank[xRoot] == rank[yRoot])rank[yRoot]++;}}}private:std::vector<int> parent;std::vector<int> rank;
};

6. 实例

684. 冗余连接 - 力扣(LeetCode)

685. 冗余连接 II - 力扣(LeetCode)

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

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

相关文章

FPGA Assembly

FASM 是一种文件格式,用于指定 FPGA 比特流中需要置1或清0的位。FASM 的设计初衷是提供一个中间层,令 FPGA 布局布线工具无需关心实际运行的 FPGA 比特流文件格式而工作。FASM 文件格式具有以下特性:从文件中删除任意一行都不会影响其有效性(注:这里的“有效性”指的是能正…

Flink 实战之维表关联

生产应用中,经常遇到将实时 **流式数据** 与 **维表数据** 进行关联的场景。常见的维表关联方式有多种,本文对以下 3 种进行了实现,并对每种方法的优缺点进行了比较:1. 预加载维表 2. 异步 IO 3. 广播维表下面分别使用不同方式来完成维表 join 的实验,附源码和实时动效。F…

Sa2VA环境搭建推理测试

引子 Sa2VA模型通过结合SAM-2和LLaVA,将文本、图像和视频统一到共享的LLM标记空间中,能够在少量指令微调下执行多种任务,如图像/视频对话、指称分割和字幕生成。该模型在视频编辑和内容创作中展现出强大的性能,在相关基准任务中达到了SOTA水平。OK,那就让我们开始吧。一、…

20-bluecms代码审计、thinkphp相关知识cve和cnvd编号申请

1、对bluecms进行代码审计,分析复现文件上传、ssti模板注入、文件删除等漏洞 文件上传审计admin/tpl_manage.php 文件发现,在do_edit模块有三个参数(act = do_edit、tpl_name = 写入文件名称、tpl_content = 写入内容,且代码中未对文件名过滤,导致可以上传任意文件。查看对…

ios SDK AB 开关切换

在数据库的这个服务器 然后再ctest1数据库新建编辑器然后查询select* fromapp_config ac whereaccess_no = 12100186 //这个是应用IDand module = abSwitchand param_name = export_otel_ab查到后,把param_value改为B,或者A,然后回车,然后点击图中的保存 保存后等两分钟,…

CS Course Learning

【李宏毅】2024大语言模型课程 课程学习课程链接:https://speech.ee.ntu.edu.tw/~hylee/genai/2024-spring.php Bilibili相关视频链接:https://www.bilibili.com/video/BV1XS411w7qrGPT: Autoregressive model In-context LearningChain of Thoughts (CoT) Tree of Thoughts …

跟着狂神学markdown作业01天

markdown学习 标题 一共可以做六级标题 格式为#+空格+标题 几级标题就打几个空格 字体 粗体:hello,world 两边各加两个*号 斜体:hello,world 两边各加一个*号 粗体+斜体:hello,world 两边各加三个***号 删除效果:hello,world 引用选择狂神说java,走向人生巅峰(用>…

java知识面试day4

1.常见的关键字有哪些static:静态变量,静态变量被所有对象共享,在内存中只有一个副本。具有静态变量,静态方法块,静态代码块(在类加载时候被指执行一次),静态内部类:非静态内部类需要依赖外部实列,但静态内部类不需要。final 基本数据类型用final修饰不能修改,引用对象被…

[QOJ 8366] 火车旅行

毒瘤边化点,有人说非排列只需要加一些细节,但是这个题毒瘤在于非排列。 statement 给定一个长度为 \(n\) 的序列 \(a_i\)。 对于位置 \(x\) 和 \(y\):若 \(y < x\) 且 \(max_{y < i < x} a_i < min(a_x, a_y)\) 则位于 \(x\) 的棋子可以花费 \(L_x\) 的代价跳到…

uipath更新到最新版本2025.0.161出现严重问题

uipath更新到最新版本2025.0.161出现严重问题:1. 打开既有项目,会报CS0246错误2. 无法创建新项目,一直报无权限访问尝试办法:1. 重新安装uipath,未解决2. 删除项目重新添加,未解决3. 给账户添加最高权限,未解决 workaround:把项目从默认文件夹复制到其他盘(除了C盘外…

Python正则表达式之re.compile函数

​在Python编程语言中,re.compile函数是正则表达式模块(re)中的一个核心组件,它负责将文本形式的正则表达式编译成一个正则表达式对象。这个对象随后可以被用来执行高效的模式匹配操作,如查找、替换或者分割字符串等。理解并有效利用 re.compile对于编写高效且可维护的正则表…

Unity Addresable打包总结第二弹

前言 前文介绍了Addressables在本地打包是怎么使用,这里介绍下怎么打远程包,并且怎么做到打增量包,Lets Go! 远程包新建一个Group,将它的 BUild & Load Paths 改为Remote,并将RemoteRes资源文件夹塞入Remote Group,其中包含一个Capsule.prefab资源:在Addressabvles …