概念
并查集: 一种可以动态维护若干个不重叠的集合,并支持合并与查询的树型数据结构。
定义集合:(代表元法)每个集合选择一个固定的元素,作为整个集合的代表。
定义归属关系:
- 普通算法:
用数组f[x]保存元素x所在的集合代表
例:两个集合(1,2,3) , (4,5) 则可将f[1],f[2],f[3]均赋值为3,将f[4],f[5]均赋值为5
可以快速查询元素的归属集合,但合并时的时间复杂度为O(n) - 并查集算法:
用树型结构存储每个集合,树上的每个节点是一个元素,树根是集合的代表元素。用数组fa保存父节点(根的父节点设为自己),即fa[x]保存x的父节点。合并集合时只需连接两个树根;但查询元素的归属时需从该元素开始,通过fa储存的值不断递归访问父节点直至到达树根
两种基本操作
并查集的初始化
int fa[SIZE];
for(int i=1;i<=n;i++){
fa[i]=i;
}
Get(查询一个元素属于哪个集合)
若x是树根,则x是集合代表,否则递归访问父节点fa[x]直至根节点
int get(int x){
if(x==fa[x]){
return x;
}
return fa[x]=get(fa[x]); //压缩路径,fa直接赋值为代表元素
}
Merge(把两个集合并为一个大集合)
合并元素x和元素y所在的集合,等价与让x的树根作为y的树根的子节点
void merge(int x,int y){
fa[get(x)]=get(y);
}
两种优化思想
路径压缩
即执行get操作,使递归时访问过的所有节点均指向根节点,可以使树的深度更低,增大查询效率,每次get操作的均摊复杂度为O(logN)
压缩前
压缩后
按秩合并
可把集合的秩看做集合的代表元素,在合并集合时将秩较小的树根作为秩较大的树根的子节点
可以另开数组a,a[R]表示以R为代表元素的节点个数,在合并时通过数组a判断秩的大小
代码实现(模板题目)
题目链接
题目描述:
有 N 个元素和 M个操作。
接下来 M行,每行包含三个整数 Zi,Xi,Yi
当 Zi=1时,将 Xi与 Yi 所在的集合合并。
当 Zi=2 时,判断Xi 与 Yi是否在同一集合内
AC代码
#include<iostream>
using namespace std;
int fa[100010];
int get(int x){ //查询元素(递归实现)
if(x==fa[x]){
return x;
}
return fa[x]=get(fa[x]);
}
void merge(int x,int y){ //合并集合
fa[get(x)]=get(y);
}
int main(){
int n,m,z,x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){ //初始化数组
fa[i]=i;
}
for(int j=1;j<=m;j++){ //执行m次操作
scanf("%d%d%d",&z,&x,&y);
if(z==1){
merge(x,y);
}
else if(z==2){
if(get(x)==get(y)){
printf("Y\n");
}
else{
printf("N\n");
}
}
}
return 0;
}