什么是并查集?
并查集是一种数据结构,用来管理集合之间的关系,可以用来进行判断是否属于同一集合或将不同的集合合并等操作。
我们用个生活中的例子来说明一下
原本小明家这个集合只有小明,小红家只有小红,那么他们结婚后(合并)就属于一个家庭(一个集合),像这样,通过并查集我们能够合并不同的集合,然后还能快速地查找他们是不是同一个集合。
并查集的基本操作
合并集合的操作(将两个不同的集合合并)
这是两个不同的集合,我们想将两个集合合并的话,那么当然就是把2和4(接下来我们叫祖宗节点,一个集合一定只有一个祖宗节点,且祖宗节点的父节点等于它本身,因为祖宗节点往上没有节点了)连接起来就形成了一个集合。
查找祖宗节点(用于判断是否属于统一集合)
根据上图,我们要想找3的祖宗节点,就要递归找3这个节点的父节点的祖宗节点,一直递归,直到找到祖宗节点,如果每次都这样查找,每次查找的时间是O(logn)也就是高度次,看起来时间并不长,如果需要大量查找的话,就会大大降低效率,所以要引出路径压缩的操作,该如何操作呢?
我们希望:在第一次查询的时候,让路径上的每个节点都与祖宗节点相连,那么虽然在第一次查询的次数为高度
次,但下一次再次查询的时候,时间复杂度就会压缩成O(1).
下面用一道模板题来实现一下并查集的操作
#include<iostream>
using namespace std;
const int N=1e5+10;
int p[N];//记录该节点的父亲节点
int find(int x){
//查找祖宗节点操作
if(p[x]!=x){
//如果该节点的父亲节点不是本身,说明不是祖宗节点
p[x]=find(p[x]);//让父节点指向最终找到的父亲节点,递归
}
return p[x];//如果不是父节点等于本身,说明是祖宗节点,返回
}
int main(){
int n,m;//n是数字个数,m是操作次数
for(int i=1;i<=n;i++) p[i]=i;
//一定要记得初始化,这样初始化的原因是因为一开始并没有集合合并
//每个节点的父节点都是自己,也就是是说大家都是祖宗节点,都是不同的集合
while(m--){
char op[2];int a,b;
scanf("%s%d%d",op,&a,&b);
if(*op=='M'){
p[find(a)]=find(b);//这个操作是指,a的祖宗节点的父节点是b的祖宗节点,目的就是让两个集合连接
}
else{
if(find(a)==find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}
以上是标准并查集的一些基本操作,接下来来介绍两个标准并查集的变种:带权并查集和扩展域并查集。并用一道经典例题来介绍这两种并查集的使用。
例题如下:
</