基本并查集问题

并查集

大致概念

一种解决元素分组和不相交集合合并,查询问题的数据结构
大量的树(单个节点也算是树)经过并根生成一系列家族(即森林)的过程。每个集合即每棵树都是由唯一根节点确定,也可以理解为每个家族的族长就是根节点(递归最后一层的结点)。

如:有16个结点(树),a结点所在的树的根是a,而c的根是c,我想让ac并根,即c所在的树的根合并到a所在的树的根下,称为a树的子节点,从今以后,c的根不再是c,而是a.
同理,我让数字类,与字母类分别并根
在这里插入图片描述
逻辑关系如下
在这里插入图片描述

并查集的存储结构

并查集采用数组表示整个森林
并查集数组: int bcj[Maxn+1] 对于bcj[x]=y,有:
x是某个结点,y是这个节点所在树的根,初始化并查集数组时自己即为所在森林的树根(f[x]=x)。

// 假设初始有860个结点
# define Maxn 860
int bcj[Maxn+1];//并查集数组f[x]=y,x是某个结点,y是这个节点所在树的根
void bcj(){//并查集的初始化
    for(int i =0;i<=Maxn; i++)bcj[i]=i;//当然为了简明,bcj可写为fa/root表示存的是该点的树根
}

并查集的查询

递归法:递归地访问父节点,直到访问到根结点处(因为代码实现中,访问到根结点处,查找根节点的父亲是否是自己,即查询自己是否是根节点,如果是,则返回)
对于某个并查集中的任意结点,通过查询能找到其根节点

//这种方法容易导致某个并查集树的高度很长
int find(int x){
    if(fa[x] == x)return x;
    else return find(fa[x]);
}

但是,如果每次都查询最后一个,那么他就要经过多次递归,非常消耗时间,这时候我们就要引入路径压缩
在这里插入图片描述

路径压缩

路径压缩是为了解决当树的高度过高的时候,提高查询时效的方法。
解决方式也很简单,在递归的同时将路径压缩,那么上面的图经过一次查询后的效果如下。
在这里插入图片描述

int find(int x){
    if(x == fa[x])return x;
	else{
        fa[x] = find(fa[x]); //父节点设为根节点
        return fa[x]; //返回父节点
    }
}

判断两个元素是否是亲属(属于同一个集合)
判断 A,B 是否属于一个集合直接判断 isBro=(find(A)== find(B)?YES:NO);

合并(核心)

把一颗树的根节点设置为另一棵树的根节点
还有一种方式是按秩合并,但是我们使用路径压缩时间复杂度就已经很低了,此处不做讲解。
例如:合并前
在这里插入图片描述
合并后:
在这里插入图片描述

void merge(int i, int j){
    fa[find(i)] = find(j);
}

启发式合并

合并操作避免发生退化:
合并时,选择哪棵树的根节点作为新树的根节点影响未来操作的复杂度
我们可以按照子树大小去合并,小的合并到大的,注意是合并!
所以启发式合并的原理是在集合合并时 将小的集合 合并到 大的集合里,也可以使 find 操作复杂度降低到 O(logn),在集合合并时还要增加一个更新集合大小的操作。

//启发式合并
void merge(int x,int y){
    x=find(x);y=find(y);
    if(x!=y){
        if(sz[x]<sz[y])swap(x,y);//代码逻辑不管:确保第一参数是相对大的集合
        sz[x]+=sz[y];//大集合扩容,即大集合空间加上小集合的大小
        fa[y]=x;//并根:小集合的根为x
    }
}

并查集模板

#include<bits/stdc++.h>
using namespace std;
#define MAXN 2000000
int fa[MAXN+1];//并查集采用数组表示整个森林,初始时每个森林的树根为自己。
//并查集初始化
void bcj(){
	for(int i=0;i<=n;++i)fa[i]=i;
} 
//查找 
//int find(int x){
//	if(fa[x]==x)return x;
//	else return find(fa[x]);
//}
//查找+路径压缩(可以让树的高度降低为1) 
int find(int x){
	if(x==fa[x])return x;
	else{
		fa[x]=find(fa[x]);//父节点设为根节点 
		return fa[x];//返回父节点 
	}
} 
//合并 
void merge(int i,int j){
	fa[find(i)]=find(j);
}
//启发式合并
//void merge(int x,int y){
//    x=find(x);y=find(y);
//    if(x!=y){
//        if(sz[x]<sz[y])swap(x,y);
//        sz[x]+=sz[y];
//        fa[y]=x;
//    }
//}
int main(){
	
	return 0;
}

例题

在这里插入图片描述
例如:
5×4 的小格子,编号:

1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20

输出描述
输出植物数量。

输入输出样例

输入
5 4

16

2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17

输出
5
合根参考
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define MAXN 2000000
int fa[MAXN+1];
int n,m;//n行m列 
int k;//k次并根 
void bcj(){
	for(int i=0;i<=MAXN;++i)fa[i]=i;
}
int find(int x){//查找+路径压缩 
	if(x==fa[x])return x;//如果查找的元素的根就是自己,返回 
	else{//如果不是,递归地往上搜索根节点 
		fa[x]=find(fa[x]); //将根节点赋给这个元素的并查集 
		return fa[x];//返回这个元素的并查集(该元素的根) 
	}
}
void merge(int i,int j){//前者并根到后者,后者为根 
	fa[find(i)]=find(j);//合并,将i元素的根改为j元素的根 
}
int main(){
	ios::sync_with_stdio(0);cin.tie(nullptr);cout.tie(nullptr);
	bcj();
	cin>>n>>m>>k;
	int a,b;
	for(int i=1;i<=k;++i){
		cin>>a>>b;
		merge(a,b);//并根 
	}
	int ans=0;
	for(int i=1;i<=n*m;++i){//查询有多少个元素的根仍然是自己(即存在多少个并查集) 
		if(fa[i]==i)++ans;
	}
	cout<<ans;
	return 0;
}

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值