算法学习笔记(18)——并查集(Disjoint-Set)
并查集(Disjoint-Set)
并查集(Disjoint-Set)是一种可以动态维护若干个不重叠的集合,并支持合并与查询的数据结构。详细地说,并查集包括如下两个基本操作:
- Get,查询一个元素属于哪个集合。
- Merge,把两个集合合并成一个大集合。
为了具体实现并查集这种数据结构,我们首先需要定义集合的表示方法。在并查集中,我们采用“代表元”法,即为每个集合选择一个固定的元素,作为整个集合的“代表”。
其次,我们需要定义归属关系的表示方法。第一种思路是维护一个数组 ,用 保存元素 所在集合的“代表”。这种方法可以快速查询元素的归属集合,但在合并时需要修改大量元素的 值,效率很低。第二种思路是使用一个树形结构存储每个集合,树上的每个节点都是一个元素,树根是集合的代表元素。整个并查集实际上是一个森林(若干棵树)。我们仍然可以维护一个数组 来记录这个森林,用 保存 的父节点。特别地,令树根的 值为它自己。这样一来,在合并两个集合时,只需连接两个树根(令其中一个树根为另一个树根的子节点,即 )。不过在查询元素的归属时,需要从该元素开始通过 存储的值不断递归访问父节点,直至达到树根。为了提高查询效率,并查集引入了路径压缩与按秩合并两种思想。由于按秩合并的优化效果并不明显,本文仅讨论路径压缩这一优化策略。
我们注意到,第一种思路(直接用数组 f 保存代表)的查询效率很高,第二种思路的合并效率很高,所以不妨将两者结合起来。实际上,我们只关心每个集合对应的“树形结构”的根节点是什么,并不关心这棵树的具体形态,这意味着下面两棵树是等价的。
因此,我们可以在每次执行 Get 操作的同时,把访问过的每个节点(也就是查询过的元素的全部祖先)都直接指向树根,即把上图中左边那棵树变成右边那棵。这种优化方法被称为路径压缩。采用路径压缩优化的并查集,每次 Get 操作的均摊复杂度为 。
模板代码:
- 并查集的存储:使用一个数组 p 保存父节点(根的父节点设为自己)。
int p[N];
- 并查集的初始化:设有n个元素,起初所有元素各自构成一个独立的集合,即有 n 棵 1 个点的树。
for (int i = 1; i <= n; i ++ ) p[i] = i;
- 并查集的 Get 操作:若 x 是树根,则 x 就是集合代表,否则递归访问 p[x] 直至根节点。
int get(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; }
- 并查集的 Merge 操作:合并元素 x 和元素 y 所在的集合,等价于让 x 的树根作为 y 的树根的子节点。
void merge(int x, int y) { p[find(x)] = find(y); }
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int p[N];
int get(int x)
{
if (p[x] != x) p[x] = get(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) p[i] = i;
while (m -- ) {
char op;
int a, b;
cin >> op >> a >> b;
if (op == 'M') p[get(a)] = get(b);
else
if (get(a) == get(b)) puts("Yes");
else puts("No");
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南