关于并查集,并查集(Disjoint-Set)是一种可以动态维护若干个不重叠的集合,并支持合并与查询两种操作的一种数据结构。
合并:将两个集合合并;
查询:询问两个元素是否在一个集合当中。
我们将每个集合都用一棵树来表示,树根的编号便是整个集合的编号,通过每个节点来存储它的父节点,我们用p[x]来表示父节点。
那么便出现了几个问题:
1.如何判断树根?
2.如何求x的集合编号?
3.如何合并两个集合?
1.判断树根
if(p[x]==x)
2.求解x的集合编号
while(p[x]!=x) x=p[x];
这是通过父节点一次次向上访问
这样的解决方法一旦遇到极深的树,必然会超时,所以我们用路径压缩进行优化;
一级一级的查询容易浪费时间,所以我们可以将每个节点的上级都设置为根节点来降低树的高度,这就是路径压缩
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);//相当于先找到根节点,再p[x]==根节点;
return p[x];//递归的出口
}
3合并两个集合
通过将两个看作树,我们不难想到,将一棵树的根移到另一颗树上,就成了一棵树。
那么实现的代码如下
void join(int x,int y)
{
int fx,fy;
fx=find(x),fy=find(y);//查找x,y的根节点;
if(fx!=fy)//根节点不同
p[fx]=fy;//将树x移到树y上
}
接下来我们对其进行应用,下面是一道acwing的例题以及ac的代码
https://www.acwing.com/problem/content/838/
#include<iostream>
using namespace std;
int n,m,p[1000010];
int find(int x)//查找函数
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void join(int x,int y)//合并函数
{
int fx,fy;
fx=find(x),fy=find(y);
if(fx!=fy)
p[fx]=fy;
}
int main()
{
scanf("%d%d",&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[0]=='M') join(a,b);
else
{
if(find(a)==find(b)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
return 0;
}
关于查询某个集合的长度
以上题为例
只需要添加一个数组size来记录长度,在合并的时候将长度加在根节点上即可,如下
void join(int x,int y)//合并函数
{
int fx,fy;
fx=find(x),fy=find(y);
if(fx==fy) return; //处于同一集合直接退出
size[fy]+=size[x];//将集合x的长度加到集合y上
if(fx!=fy)
p[fx]=fy;
}
需要注意的是只有根节点上的size是有效的。
关于并查集的应用
由于并查集只有两个操作,“并” 和 “查”,通过这两个操作可以延申出一些其他的应用:
- 图的连通性问题
- 集合的个数
- 集合中元素的个数
大致内容就是这些了.....吧?别的我也不会了(摊手)
都是学长的任务罢了.JPG