并查集 Disjoint Set Union (DSU)
并查集的功能
- 将两个集合合并
- 询问两个元素是否在一个集合当中
基本原理
每个集合用一棵树来表示。树的编号就是整个集合的编号。每个节点存储它的父节点,p[x]表示x的父节点。
三个问题
- 如何判断树根
p[x] = x
- 如何求元素x的根节点
while(p[x] != x) x = p[x]
- 如何合并两个集合,比如合并元素
x
和y
所在的集合 找到x的根节点p[x]
,p[y]
的根节点p[y]
,p[px] = py
并查集核心操作find()
数组p[N]
当中保存的是结点x
的父元素,也就是p[x]
形式如同上图,每个结点都记录父元素的下标值,而跟结点的信息是元素所在集合的信息。
如果要想要寻找结点x属于那个集合,就需要不断向上寻找双亲的信息,最终找到根结点,也就是集合的信息。
在寻找集合信息的过程中,可以进行优化:
假设我们需要寻找x的所在集合,就需要不断的向上寻找父结点,判断父结点的信息,最后找到根结点也就是集合信息为止。但是在此过程中,可以将中途遍历过的结点的父结点信息都替换成根结点的信息,这样在下次查询的时候就可以直接访问x的父结点,将时间复杂度降低致O(1)
。
所以 find()
需要实现两个操作 :
- 寻找集合信息
- 将路径压缩
// p[N]用作保存树的各个结点的信息
// 迭代写法
public static int find(int x ){
//因为p[x]中存的是x的父节点的索引 所以一直迭代找到 x = p[x]的节点 即根节点(祖宗节点)
int r = x;
while(r != p[r]) r = p[r];
//路径压缩 将这一条路上的所有节点都直接指向根节点(祖宗节点) 此时的r就是祖宗节点
while(x != p[x]){
//存一下父节点
int t = p[x];
//直接指向根节点
p[x] = r;
//走到存储的父节点的位置继续做
x = t;
}
return r;
}
// 递归写法
public static int find(int x) { // 合并 + 压缩过程
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
模板题
acwing 837. 连通块中点的数量 (需要根据题目信息额外维护一些信息)
acwing 240. 食物链(需要根据题目信息额外维护一些信息)