1.定义
并查集就是一种有查询元素所在集合和合并两个集合的数据结构
2.代表元
代表元可以理解为集合的“名字”,文章用树来表示集合,此处可设置代表元为每颗树的根节点
3.查和并
查询就是查询当前元素的代表元,这里设一个节点的代表元是它的父节点,根节点的代表元是它本身
合并就是把两个集合的代表元合并成其中一个
4.路径压缩和按秩合并
路径压缩就是把这棵树的所有节点的代表元都设置成自己父节点的代表元,从根节点开始,最后结果是所有节点的代表元都是这个树的根节点,方便查找
按秩合并中,秩就是规则,一般和集合大小有关,比如树的高度(n个节点的二叉树高度为log
以2为底,n+1为真数的对数向上取整)
按秩合并就是根据秩的大小,小的合并到大的里面去,就是小的代表元变成大的代表元
一般只用路径压缩
5.操作
(1)创建并查集
int f[N];//记录每个集合的代表元,对于树就是记录每个点的父节点
(2)初始化
for(int i=1;i<=n;i++) f[i]=i;//初始化,先把每个点的父节点设置为自己,就是代表元是自己
(3)查找操作(加了路径压缩)
int get(int x){//返回值是x所在树的代表元
if(x==f[x]) return x;//x代表元是自己,说明是根节点
return f[x]=get(f[x]);//x不是根节点,x的代表元就等于x父节点的代表元
}
(4)合并操作
void merge(int x,int y){
get(f[x])=f[y];//x父节点的代表元等于y的代表元
}
6.扩展域和边带权
边带权就是多一个数组来维护这棵树父子节点之间的关系
扩展域就是对于并查集的扩展
详情请见并查集,扩展域并查集,带边权并查集详解,OJ练习,详细代码_拓展域并查集-CSDN博客
例题1【模板】并查集 - 洛谷
#include<bits/stdc++.h>
using namespace std;
const int MAX=1e4+5;
int N,M;
int f[MAX];//并查集
int get(int x){
if(x==f[x]) return x;
return f[x]=get(f[x]);
} //查询
void merge(int x,int y){
f[get(x)]=get(y);
}//合并
int main(){
cin>>N>>M;
for(int i=1;i<=N;i++) f[i]=i;
//初始化
while(M--){
int X,Y,Z;
cin>>Z>>X>>Y;
if(Z==1){
merge(X,Y);
}
else{
if(get(X)==get(Y)){
cout<<"Y"<<"\n";
}
else{
cout<<"N"<<"\n";
}
}
}
return 0;
}
这里用了扩展域
#include<bits/stdc++.h>
using namespace std;
const int N=5e4*3+5;
int n,k;
int num;//记录假话数量
int f[N];
//自己:1~N 食物:N+1~2N 天敌:2N+1~3N
int get(int x){
if(x==f[x]) return x;
return f[x]=get(f[x]);//x的代表元和x父节点的代表元一样
}
void merge(int x,int y){
f[get(x)]=get(y);
}//x树根为y树根的子节点
int main(){
cin>>n>>k;
for(int i=1;i<=n*3;i++){
f[i]=i;//初始化
}
while(k--){
int tip,x,y;
cin>>tip>>x>>y;
if(x>n or y>n){
num++;
continue;
}
if(tip==1){
if(get(x)==get(y+2*n) or get(x)==get(y+n)){
//如果是捕食关系
num++;
continue;
}//条件
merge(x,y);//并到同类上
merge(x+n,y+n);//并到天敌上
merge(x+2*n,y+2*n);//并到食物上
}//同类
else{
if(get(x)==get(y) or get(x+2*n)==get(y)){
//如果是同类或者是关系反了
num++;
continue;
}//条件
merge(x,y+2*n);//y的天敌是x
merge(x+n,y);//x的食物是y
merge(x+2*n,y+n);//x的天敌是y的食物
}//被吃
}
cout<<num;
return 0;
}
搜索
复制