一、概念:
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。并查集的思想是用一个数组表示了整片森林(parent),树的根节点唯一标识了一个集合,我们只要找到了某个元素的的树根,就能确定它在哪个集合里。
二、用法:
并查集用在一些有 N 个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这个过程看似并不复杂,但数据量极大,若用其他的数据结构来描述的话,往往在空间上过大,计算机无法承受,也无法在短时间内计算出结果,所以只能用并查集来处理。
简述一下:
1,将两个集合合并。
2,询问两个集合是否在同一集合中。
三、基本原理:
每个集合用一颗树来储存。树的根节点编号是每个集合的编号。每个节点存储它的父节点,p[x]表示x的父节点。
四、常见问题/要处理的问题:
1,如何判断树根:if(p[x]==x)
2,如何求x的集合编号: while(p[x]!=x) x=p[x] (对照用法二)
3,如何合并两个集合:px是x的集合边界,py是y的编号集合。p[x]=y
------------------------------------------------------------*--------------------------------------------------------------------
题目:
一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。
现在要进行 mm 个操作,操作共有两种:
M a b
,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;Q a b
,询问编号为 a 和 b 的两个数是否在同一个集合中;输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为
M a b
或Q a b
中的一种。输出格式
对于每个询问指令
Q a b
,都要输出一个结果,如果 a 和 b 在同一集合内,则输出Yes
,否则输出No
。每个结果占一行。
数据范围
1≤n,m≤10^5 1≤n,m≤10^5
输入样例:
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:
Yes
No
Yes
先上代码:
#include<iostream>
using namespace std;
const int N = 100010;
int p[N];
int find(int x){
if(p[x]!=x){
p[x]=find(p[x]);
}
return p[x];
}
int main(){
char op[2];
int n,i,j,k,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
p[i]=i;
}
while(m--){
int a,b;
cin>>op>>a>>b;
if(op[0]=='M'){
p[find(a)]=find(b);
}
else{
if(find(a)==find(b)){
cout<<"Yes"<<endl;
}
else{
cout<<"No"<<endl;
}
}
}
return 0;
}
解读思路:
初始化:由于一开始我们得到的是几个散乱的集合,即每个数都是一个集合,所以我们初始化为 p[x]=x ,这就表示他自己就是一个集合,也可以说成他就是自己的父节点/祖宗节点。
集合合并与查找:假如我们有{1},{2}这两个单独集合,我们要把它合成一个集合,就需要把他们的祖宗节点变成一个,就是上面说到的 p[x]=p[y]=x ,(注:这里以谁为父节点是按你输入的顺序定的马,可以理解为随机的,例如你第一个数是1,那吗之后的想要把其他数都合并在1这个数的集合里,那么他们的父节点为 p[1]=p[2]=p[3]=....p[n]=1 )之后集合就成了{1 , 2}。那么我们的查找操作也相应完成了,既然都在一个集合里,都有一个父节点,判断两个数是否在一个集合里直接比较父节点即可,高效准确。
find()函数:这里建议自己模拟一遍。如果此时我们已经将1,2合并到一个集合中去,此时把1再执行一边find函数,根据代码:p[1]=2 -> !=1 -> p[1]=find(2) -> p[2]=2 -> return 2 结束函数。
好,我们加一问,询问某个数所在集合中的元素数量。
代码:
#include<iostream>
using namespace std;
const int N = 100010;
int p[N];
int siz[N];
int find(int x){
if(p[x]!=x){
p[x]=find(p[x]);
}
return p[x];
}
int main(){
char op[2];
int n,i,j,k,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
p[i]=i;
size[i]=1;
}
while(m--){
int a,b;
cin>>op;
if(op[1]=='1'){
cin>>a>>b;
if(find(a)==find(b)){
cout<<"Yes"<<endl;
}
else{
cout<<"No"<<endl;
}
}
else if(op[0]=='C'){
cin>>a>>b;
if(find(a)==find(b)){
continue;//注意特判,否则重复相加个数
}
siz[find(b)]+=siz[find(a)];
p[find(a)]=find(b);
}
else{
cin>>a;
cout<<siz[find(a)]<<endl;
}
}
return 0;
}
思路大致相同,要找某个数所在集合中元素的个数,我们也只需找到他的父节点的标号下的元素个数即可。我们需要开一个size[]数组存放元素个数,例如还是举上面的例子:1,2,3在同一个集合里,他们的父节点是p[2]=2,那么他们的size的值也都变成size[2]=size[3]=size[1]=3,另外这个数组也是需要维护更新的,我们看具体操作:先初始化,size[i]=1,之后相加即可。为什么siz[]里面的参数是find(x)呢,原因还是要找到相同的父节点。这里我们可以思考一下为什么先两个集合个数先加再合并?模拟一下就知道了。