今天没有写博客,就想着把之前一直想写却没来得及写的并查集写一下,对于并查集,算我掌握比较好的算法
What is 并查集?
关于并查集的定义,百度百科的解释为
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中。其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。
Why 并查集?
在输入时,每一个点都是一个只有一个单元素的集合,并查集的作用就是把相连的边并与一个集合中,成为一棵树
具体操作就是对于点i与点j相连的边有father[i]=j,既将边的一个节点设为另一个节点的父节点,这样就把这两个节点连接起来,放入在一个集合中。
例题详解
以上所有所说的都是空泛而不实用的,具体的内容靠一道例题来讲解
亲戚
问题描述
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
输入
第一行:三个整数 n,m,p (n<=5000,m<=5000,p<=5000)
分别表示有n个人,m个亲戚关系,询问p对亲戚关系
以下m行:每行两个数M[i],M[j],1<=M[i],M[j]<=n,表示M[i],M[j]具有亲戚关系。
接下来的p行:每行两个数P[i],P[j],询问P[i]和P[j]是否具有亲戚关系;
输出
共p行 每行一个“Yes“或”No“。表示第P[i]个询问的答案为”有“或者”没有“亲戚关系。
输出样例: 输出样例:
6 5 3 Yes
1 2 Yes
1 5 No
3 4
5 2
1 3
1 4
2 3
5 6
这道题可以被看做并查集的模板,从这道题中我们就很清楚明白,将有亲戚关系的点放入一个集合中,然后查看这两个点是否在同一个集合中,即是否具有亲戚关系;
那么实现将两个点放入同一个集合中的操作就是建树,然后看这两个节点是否在这个树中;
具体操作
先将数组初始化
for(int i=1;i<=n;i++) father[i]=i;
将每一个节点的父节点设为他自己
for(int i=1;i<=m;i++){
father[m[i]]=m[j];
}
之后,每一个点的父节点都是另一个点,之后就建立了一棵树
int find_pre(int i){
if(father[i]!=i) return find_pre(father[i]);
//当father[i]不是根节点,就在找该点的父节点
}
追后,通过该函数找到根节点,如果,根节点相同,就说明是亲戚
问题&&改进
到这里,可能就会认为,并查集就是这?就结束了??
当然没有!
试想,在上述样例中,有点(1,3),点(1,4)
如果实行上述代码,就会出现一个问题father[1]=3,father[1]=4;
下一个点直接将上一个点覆盖掉,如此下来,最后集合必然少一个点
而且在每一次都要遍历一遍询问点以上的每一个点,浪费时间
遇到问题就要解决问题
那么解决方法就很简单
每一个点都直接接在根节点上,那么这棵树深度就为2;
具体方法
int find_pre(int x){
if(father[x]!=x) return father[x]=find_pre(father[x]);
//找根结点的时候,顺便将该点连在根节点上;
else return x;
}
void Union(int x,int y){
int fa=find_pre(x);
int fb=find_pre(y);
if(fa!=fb) faturn[fa]=fb;//如果两个点不再同一棵树上,就把这两点的根节点相连
}
结合这道题
思路很明白,就是像上面一样
我就直接上代码了
#include<bits/stdc++.h>
#define M 200005
using namespace std;
int a[M];
int find_pre(int x){
if(a[x]!=x) return a[x]=find_pre(a[x]);
else return x;
}
void ans(int c,int d){
if(find_pre(a[c])==find_pre(a[d]))
cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
void Union(int e,int f){
int ee=find_pre(a[e]);
int dd=find_pre(a[f]);
if(ee!=dd) a[dd]=ee;
}
int n,m,k;
int main (){
ios::sync_with_stdio(false);
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
a[i]=i;//初始化
}
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
Union(x,y);//加入边
}
for(int i=1;i<=k;i++){
int q,p;
cin>>q>>p;
ans(q,p);//答案
}
return 0;
}
直接AC了