并查集
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,模板就比较稳定。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!














