并查集启发式合并

        我们知道,在并查集中为了减少时间复杂度,我们引入了路径压缩,路径压缩的原理说白了就是在find函数回来的路上顺便把本节点指向根节点。然而路径压缩也是有效率的,这个效率取决于本节点到根节点的路径长度。有没有一种方法可以更好的节省路径压缩的时间呢?有!这种方法就是启发式合并。启发式合并可以根据树的大小进行合并,也可以根据树的深度进行合并,但树的深度合并用处不大,主要是为了后期的可持久化算法,所以在这里介绍根据大小进行启发式合并。

        我们引入这道题来介绍启发式合并。

        AC代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,p,x,y;
int size[5002];
int father[5002];
int find(int _x){
	if(father[_x]!=_x)father[_x]=find(father[_x]);
	return father[_x];
} 
inline void union(int a,int b){
	a=find(a);b=find(b);
	if(a==b){
		return;
	}
	if(size[a]>size[b]){
		father[b]=a;
		size[a]+=size[b];
	}else{
		father[a]=b;
		size[b]+=size[a];
	}
	return;
}
int main(){
	scanf("%d %d %d",&n,&m,&p);
	for(int i=1;i<=n;i++){
		father[i]=i;
		size[i]=1;
	}
	for(int i=1;i<=m;i++){
		scanf("%d %d",&x,&y);
		union(x,y);
	}
	for(int i=1;i<=p;i++){
		scanf("%d %d",&x,&y);
		int r1=find(x);
		int r2=find(y);
		if(r1==r2)printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

        观察代码便可发现,启发式合并仅仅是增加了一个size数组,具体怎么实现,让我们先看看初始化部分:

for(int i=1;i<=n;i++){
	father[i]=i;//指向自己,这一句不用说
	size[i]=1;//size[i]表示以节点为根的树的大小
}

        初始化时,所有点都是自成集合的,根节点为本身,所以size自然都设置为1;

inline void union(int a,int b){
	a=find(a);b=find(b);
	if(a==b){//如果a与b的根节点相同,那么就不进行联通,实际上这句用处不大,因为很多题目都保证给定两点不连通
		return;
	}
	if(size[a]>size[b]){//将树的大小小的接到大的上
		father[b]=a;
		size[a]+=size[b];//更新根节点树大小
	}else{
		father[a]=b;
		size[b]+=size[a];
	}
	return;
}

        重点在于union函数,size数组在这里发挥作用:每次合并时,我们都将小树接到大树的根上,这样,造成的效果就是:减少了路径压缩的长度,提高了代码的运行效率。合并树之后别忘了更新根节点的size值!

        总结:并查集是一种用途广泛且码量很少的算法,对我个人而言很喜欢这种算法,而且他与其他算法配合也能发挥出很大的威力。对于很多卡常的题目,加入启发式合并能更好的节省时间。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值