开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情
并查集
“并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。”
1.将两个集合合并
2.询问两个元素是否在一个集合中
原理
每个集合用一个树进行表示,树的根节点就是该集合的编号,
每个节点存储它的父节点,p[x]=父节点
一直追溯到它的父节点,判断是否相同,则是同一集合
问题1:如何判断树根
if(p[x]==x)
问题2:如何求X的集合编号
问题3:如何合并两个集合
p[x]是x的集合编号,p[y]是y的集合编号,p[x]=y
1.模板介绍
1)初始化
int p[N];//从来表示每个集合的根节点,老祖宗!
//一开始都是不相交的集合,每个数自成集合,根节点肯定就是自己
for(int i=1;i<=n;i++){
p[i]=i;
}
2)查找老大节点
int find(int x){
//我不是帮派的老大,那肯定头上还有老大,那就是继续向上查找
//这里有个路径压缩问题,find(p[x])进行了一个递归,所以最终结果就是根节点,所以此时的节点就直接指向了根节点,减少了中间节点的遍历时间
//就比如你和你老大之间还有个二当家,那么不管他,我直接和老大对话
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
优化:路劲压缩,如果找到根节点,那么子节点全部指向根节点,这样下次查找的效率就成为了O(1)
3)合并两个集合
//如果合并两大家族呢? 那就是认同一个祖宗,你老大和我老大是同一个,我们就是兄弟了
p[find(x)]=find(y)
4)实操演练
一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。
现在要进行 m 个操作,操作共有两种:
M a b,将编号为 a和b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;Q a b,询问编号为 a 和b 的两个数是否在同一个集合中;
#include<iostream>
using namespace std;
const int N=1e5+10;
int n,m;
int p[N];
//不止有返回根节点的用户,还进行了路径压缩
int find(int x)
{
//如果只返回根节点,不进行路径压缩,那么导致超过内存限制,进行路径压缩的原因,我们只需要判断集合根节点是否相同确定两个是否为同一个集合
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main()
{
cin>>n>>m;
//初始化 每个集合只有自己
for(int i=1;i<=n;i++)
{
p[i]=i;
}
while(m--)
{
char op;
int a,b;
cin>>op>>a>>b;
//find返回根节点
if(op=='M')p[find(a)]=find(b);
else if(find(a)==find(b))cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}