并查集

661 字
3 分钟
并查集

并查集#

并查集用来维护“若干个元素属于哪些集合”。

它最常见的两个操作是:

  • 查询两个元素是否在同一个集合。
  • 合并两个元素所在的集合。

1、可以解决什么问题#

比如有 1 ~ 5 五个点,刚开始每个点都是单独集合:

{1} {2} {3} {4} {5}

执行:

合并 1 和 2
合并 2 和 3

之后集合变成:

{1, 2, 3} {4} {5}

这时查询 1 和 3 是否连通,答案就是是。

2、核心思想:用父节点表示集合#

并查集用数组 p 表示每个点的父节点。

如果:

p[x] == x

说明 x 是这个集合的根节点,也叫祖宗节点。

两个点是否在同一个集合,就看它们的祖宗节点是否一样。

3、查找祖宗节点#

基础写法:

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

但这样树可能很深,所以通常加路径压缩:

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

路径压缩会把查询路径上的点直接挂到根节点下面,后续查询更快。

4、合并两个集合#

合并 a 和 b 所在集合:

p[find(a)] = find(b);

意思是:让 a 的祖宗节点指向 b 的祖宗节点。

5、C++ 模板#

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m;
int p[N];
int find(int x) {
if (p[x] != x) p[x] = find(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[find(a)] = find(b);
} else {
if (find(a) == find(b)) cout << "Yes" << endl;
else cout << "No" << endl;
}
}
return 0;
}

6、带集合大小的并查集#

有些题会问某个集合里有多少个点,可以多维护一个 sz 数组。

int p[N], sz[N];
for (int i = 1; i <= n; i++) {
p[i] = i;
sz[i] = 1;
}
void merge(int a, int b) {
int pa = find(a);
int pb = find(b);
if (pa != pb) {
p[pa] = pb;
sz[pb] += sz[pa];
}
}

查询集合大小时:

sz[find(x)]

7、容易写错的地方#

(1)初始化不能漏#

for (int i = 1; i <= n; i++) p[i] = i;

刚开始每个点都是自己的祖宗。

(2)合并的是祖宗,不是普通节点#

错误写法:

p[a] = b;

正确写法:

p[find(a)] = find(b);

(3)查询也要用 find#

find(a) == find(b)

不能直接比较 p[a] 和 p[b]。

原图笔记#

总结#

并查集可以记住两个函数:

find:找祖宗
merge:合并集合

路径压缩是并查集高效的关键。只要所有查询和合并都通过 find,模板就比较稳定。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

并查集
https://blog.tjcheng.cn/posts/migrated/old-90/
作者
TengJinCheng
发布于
2025-12-04
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
TengJinCheng
Hello, I'm Teng.
公告
欢迎来到TengJinCheng工作铺!
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
分类
标签
站点统计
文章
157
分类
7
标签
114
总字数
109,939
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.13.4
文章许可
CC BY-NC-SA 4.0

文章目录