【笔记】并查集
条评论不管后面写了啥,一定要把最重要的写在前面:(总记不住)
- 使用并查集前 一定要初始化、一定要初始化、一定初始化
- 用 findFather(i) 找根结点,不是 father[i] 数组
并查集的基本操作
使用数组表示并查集:
1 | int father[N]; |
father[i]
表示元素 i
的父亲结点
初始化
初始情况下,每个元素都是单独的集合,因此其父结点是自己,这在并查集中被称为 Reflexive
1 | for (int i = 1; i <= N; i++) { |
查找与路径压缩
一个集合中只有一个根结点,因此反复地查找父结点,直到 father[x] == x
,则 x
为该集合的根节点。
递推与递归方式查找根(无路径压缩)
1
2
3
4
5
6
7
8
9
10
11
12// 递推方式
int findFather(int x) {
while (x != father[x]) {
x = father[x];
}
return x;
}
// 递归方式
int findFather(int x) {
if (x == father[x]) return x;
return findFather(father[x]);
}简单的查找方式非常简单,但是对每个结点而言,要查找根总需要反复地向上找,比较费时,所以更好的办法是在查找根结点的同时,在路上把所有
x
的爸爸爷爷结点直接指向根。这样后面再找的时候时间就是O(1)
了。递归方式查找根并压缩路径
简单分析:
- 若当前结点的父亲结点等于自己则表示找到了集合的根
- 若不相等,则递归地查找其父结点的根。同时,将当前结点的父结点直接指向根结点
1
2
3
4
5
6
7
8
9
10int findFather(int x) {
if (x == father[x]) return x;
int fa = findFather(father[x]);
father[x] = fa;
return fa;
}
或
int findFather(int x) {
return x == father[x] ? x : father[x] = findFather(father[x]);
}迭代的方式查找根结点与压缩路径
1
2
3
4
5
6
7
8
9
10int findFather(int x) {
int a = x;
while (x != father[x]) x = father[x];
while (z != father[z]) {
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
合并集合
将两个集合合并在一起,只需要找到两个集合的根,将其中一个根的父结点设为另一个根即可。因为和 C/C++ 中的 union
关键字冲突,所以函数写成了 uni
。
1 | void uni(int a, int b) { |
并查集解题中的习惯操作
并查集有一些基础的常规操作,为了缩短在编程考试中减少对“常规”操作的思考时间(虽然也很简单),故记录下来,形成自己习惯性的模板代码。
对象散列编号
题目中常常给定的元素编号是连续的,通常是 1~N ,因此可以直接将对象 id 作为下标,表示其在并查集中的位置,但是有时候给定的是字符串,或是给定的范围很大,这个时候需要对每个元素重新编号,最好的方式是 从序号1开始,用两个map分别做 id->num 和 num->id 的映射 。
1 | int main() { |
用 unordered_set 记录序号非连续元素
有时候题目给定了元素的编号,并且这个编号在合理的范围内,但是编号不连续,因为并查集建立完成后需要遍历所有元素以找出不同集合,所以,如果是不连续的元素编号则可以使用 unordered_set
记录输入的元素,之前想过用 vector
但是可能出现重复输入一样的编号,所以不适用。
1 | unordered_set<int> s; |
遍历元素查找集合
在并查集建立好之后,需要找出各个集合对之做操作。
1 | for (int i = 1; i < pos; i++) { // 如果无需做散列,则是 1~N |
有时候需要将一个集合作为一个结构去看,其中需要保存一些题目要求的数据,并做排序。则要用到 map 和 vector
1 | struct node { ...properties... }; |
- 本文链接:https://blog.charjin.top/2020/02/01/algo-union-find-set/
- 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!
分享