教你十分钟学会并查集!

用武林门派来理解并查集是笔者学习算法知识时老师所用的方法,时至今日仍觉得非常有意思也很好去理解,因此为此写了一篇文章来与大家分享:
何为并查集?

  • 实际上,当有n个元素的集合应用问题中,我们通常是让每个元素构成n个简单的单元素集合,并按照一定的规则合并,然而,我们需要反复得去查找元素在哪个集合中,看似简单,实际上却将会花费大量的时间,若数据量极大,将会超出计算机的空间限制,因此诞生特殊的数据结构——并查集

上面已经描述了,并查集通常是用来处理节点数非常多并需要频繁判断节点关系的集合问题,因此传统的图并不适用,我们不妨将这看作武林门派:
在这里插入图片描述  如图,作为武林人士,自然是常在江湖行走闯荡,碰见一些三观不和的,自然是要出手打一架,然而这是问题就来了,假如对面是自己门派的人,那不就尴尬了,于是他们记住了自己的直接朋友,通过两两相连的朋友关系串联起来,也就形成了集合。
  之后,再在每个门派里选出一个老大,峨嵋派就选灭绝师太,武当队就选张三丰等等,这样只要比较一下两队的队长,假如是同一个队长的,那么握手言和,假如不是,那自然是你死我活。
  那么问题就来了每个人记住的是自己的朋友,而不是队长,也就是并不知道哪个是真正的队长,想要知道队长是谁还得一个一个去问:你知不知道我们队长是谁啊?你知不知道我们队长是谁啊?知道问到有人承认自己是队长为止,这样一来,又得浪费很多时间,队长的面子也十分挂不住,有时候甚至两个朋友只是互相认识,却都不知道队长是谁。
  为了解决这一个奇怪的问题,每个队的队长决定在各自的队伍中实行等级制度,每一等级的人记住自己的上级,假如碰见要打架的,就去找自己的上级,上级再去问问自己的上级,直到问到队长为止,这样效率也变得更高了。
   很快,武林人士们有发现,世上本没有永远的敌人,假如有两个门派要握手言和变成一个门派了,那应该怎么办呢?武林人士自然是尚武的,他们决定让各自门派的队长来一场对决,赢得人自然是可以继续当整个门派的队长(这段纯属扯淡,其实随便找一个或者也没事)。


到这,武林门派的故事似乎完结了,下面给予一个小例题:
在这里插入图片描述
其实每个元素刚开始都是一个门派,合并也就是两个元素(两个门派)合并的过程,demo如下:

#include<bits/stdc++.h>
using namespace std;
int f[100000];//f就是记录各自老大
int find(int a)
{
	if(f[a]!=a) return find(f[a]);//老大不是自己就继续问上级
	else return a;//如果老大是自己则已经找到了门派的老大
}
void add(int a,int b){
	a=find(a);
	b=find(b);
	if(a!=b) f[a]=b;//不是同一个老大,让第一个老大屈服一下
}
void output(int a,int b)
{
	if(find(a)==find(b)) cout<<"Y"<<endl;
	else cout<<"N"<<endl;
}
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	  f[i]=i;//最开始老大就是自己
	for(int i=1;i<=m;++i)
	{
		int z,a,b;
		cin>>z>>a>>b;
		if(z==1)
		   add(a,b);
		else 
		   output(a,b); 
	}
	return 0;
}

进行到这貌似已经非常完美了,然后还有一个问题,假如有些门派非常奇葩,弟子又多,每个人又只带了一两个下级,那么一层一层的问仍然是非常浪费时间,于是,队长又决定再对门派进行优化,让每个人记住自己,或是记住的是自己的直接下级。(路径压缩)
在这里插入图片描述
要实现这一效果(实际上不一定是只有两层的但最多只有三层),其实也非常简单,就是利用递归的记忆化,附上优化后的demo:

#include<bits/stdc++.h>
using namespace std;
int f[100000];
int find(int a)
{
	if(f[a]!=a) return f[a]=find(f[a]);
	else return a;
}
void add(int a,int b){
	a=find(a);//重新find必不可少
	b=find(b);
	if(a!=b) f[a]=b;
}
void output(int a,int b)
{
	if(find(a)==find(b)) cout<<"Y"<<endl;
	else cout<<"N"<<endl;
}
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	  f[i]=i;
	for(int i=1;i<=m;++i)
	{
		int z,a,b;
		cin>>z>>a>>b;
		if(z==1)
		   add(a,b);
		else 
		   output(a,b); 
	}
	return 0;
}

如有错误请联系更正,本人邮箱为741041552@qq.com,也可加好友一起交流学习

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值