并查集学习笔记
\text{Part 1\,\,\,Introduction / 并查集简介}
并查集(Disjoint Set),是一种图论里的数据结构。并查集,顾名思义,可以完成"并"和"查"两种操作。"并"指的是合并两个集合,"查"指的是查询元素所在的集合。
\text{Part 2\,\,\,Example / 例题引入}
题目链接:亲戚
给定
分析:
本题是一道经典的并查集题目。对于每一组亲戚(组内所有人均互相是亲戚关系),我们可以将其看作一棵树,并为其指定一个根。那么,对于两个有亲戚关系的人,它们所在的树必然相等,那么根也就相等。
想到这里我们自然的产生了一种想法:对于第
对于给定的亲戚操作,如何处理?观察可以发现,当给出
以上这个数据结构,我们就将其称为:并查集。
\text{Part 3\,\,\,Implementation / 代码如何实现?}
我们已经有了初步的想法,那么如何实现?又如何在合并过程中维护
对于这个问题,我们需要实现一个核心函数:
int find(int x) { //查找树的根
if(x 为当前树的根) 返回 x;
else 返回 find(x 的父亲);
}
将其写成
int find(int x) {
if(fa[x] == x) return x;
return find(fa[x]);
}
有了这个函数,那么另外两个操作都好办了:对于合并操作
\text{Part 4\,\,\,Std / 示范代码}
以下为根据以上讲解,写出的 P1551
的完整代码。
注意:在初始化时,每个元素都是单独的一棵树,需要将它的根设为自己。
#include <bits/stdc++.h>
using namespace std;
int fa[100005];
int getfa(int x) { //重点 find() 函数
if(fa[x] == x) return x;
return getfa(fa[x]);
}
void merge(int x,int y) { //合并
if(getfa(x) == getfa(y)) return ;
fa[getfa(x)] = getfa(y);
}
bool issamefa(int x,int y) { //查询
return getfa(x) == getfa(y);
}
int main() {
int n,m,q; cin >> n >> m >> q;
for(int i = 1;i <= n;i++) fa[i] = i; //初始化
for(int i = 1;i <= m;i++) {
int x,y; cin >> x >> y;
merge(x,y);
}
while(q--) {
int x,y; cin >> x >> y;
cout << (issamefa(x,y) ? "Yes" : "No") << endl;
}
return 0;
}
\text{Part 5\,\,\,Optimize / 优化手段}
\text{Part 5.1\,\,\,Path compression / 路径压缩}
路径压缩是一种常见的并查集优化手段。
考虑如下两张图:
对于元素
那么如何实现呢?很简单:只需将 return find(fa[x])
改为 return fa[x] = find(fa[x])
即可。
Q:为什么要这样写呢?
A:因为相当于将自己的父亲直接设为了整棵树的根,自然就达到了我们的目标。
在
\text{*Part 5.2\,\,\,Merge by rank / 按秩合并}
按秩合并是一种在合并时的优化。
这种优化在合并时,会将深度更小的那一棵树合并到更大的那一棵树上,这样不仅能降低时间复杂度,还能尽量减少树的深度。
\text{Part 6\,\,\,Summarize / 总结}
并查集是一种基本操作较为简单,应用广泛的数据结构,使用它可以解决许多与集合合并等相关的问题。
\text{Part 7 Practice / 习题}
-
洛谷 P1892 [BOI2003] 团伙
-
一本通 1346 亲戚
-
一本通 1389 亲戚
-
一本通 1385 团伙