学渣都能看懂的-并查集(快速掌握版)

本文深入讲解并查集的概念,通过生动的故事比喻解释查集和并集操作,介绍并查集在处理元素集合关系中的应用,包括查找元素所属集合和合并集合的算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

并集:将两个集合并成一个集合
查集:查找某个元素属于哪个集合

并查集就是并集和查集两个操作。

先讲一个小例子,假如年级里每个班中,班里的每个同学只认识身边的几个同学,只知道班里有身边的几个同学,而不认识有其他同学。除了班长,其他同学一概不知道自己的班长是谁。
故事一:你想知道我们的是否在同一个班,你要怎么做?
你问你身边的同学:“我们的班长是谁呀?”那个同学说:“我也不知道呀,我要去问问我身边同学。”同学身边的同学说:“我也不知道啊?我也要问我身边的同学。”
如此循环,直到问到了班长, 班长告诉问他的同学:“你们的班长是我。”那个同学于是告诉了往回告诉了问他的同学。如此循环,直到告诉了你。这下你知道了你的班长是谁。但你没法判断我们的班长是否为同一个人?所以我也像你一样问出了我的班长是谁。这下我们就知道我们的班长是否为同一个人,于是我们可以判断我们是否在同一个班了。这就是查集。
故事二:你与我不知道我们是否在同一个班。但是如果我们在不同的班,我们想要让两个班的班长把两个班合并起来,组成一个更大的班,并让我的班长继续做班长,你的班长做普通同学。我们要怎么做?
我们通过刚才询问的方式找到了各自的班长:
1.我们的班长是同一个人,不用合并
2.我们的班长不是同一个人,合并两个班,让我的班长成为你的班长身边的同学。
这就是并集。并集操作里有查集操作

多个集合中有多个元素,每一个元素都有其独立的特征。任意元素只能判断其相邻的元素(图中有直接边相连)与其同属于一个集合,而无法判断非相邻的其他元素(无直接边相连接)与其是否同属于一个集合。由于每一个元素都带有独立的特征,故每一个元素都能代表其所在集合。故我们可以在每个集合中任意选出一个代表元素,其集合中的其他元素通过不断寻找与其相邻的元素,最终找到这个代表元素,从而知道这个元素属于哪个集合。

例如:你想要知道你们在几班,你要通过不断询问同学的方式,直到问到了班长,才能知道自己是几班。但是大家都是独立的个体,一个班的同学之间没有什么上下级关系,谁做班长,其他同学都能通过询问的方式知道自己在几班。为了区分不同班级,我们人为地选出了班长。

我们用一个Classmate数组来储存我们身边的同学,一开始还未合并班级,每个班级只有一个同学,每个同学都是班长,我们将数组初始化为-1,用-1来代表班长。

代码如下:

#include<stdio.h>
int vnum, arcnum;//元素数量和元素关系数量
int Classmate[100];//身边的同学

int Find_Class_Monitor(int mate)//查集
{	
	//如果可能会有非序号内元素,可添加判断代码
	//if(mate < 1 || mate > vnum)//该元素在序号外,无该元素
	//	return 0;

	if ( Classmate[mate] == -1)//找到了代表元素
		return mate;//返回代表元素
	return Find_Class_Monitor(Classmate[mate]);//没找到代表元素就继续找
}

void AddClass(int v1, int v2)//合并集合
{
	int v1_Class_Monitor = Find_Class_Monitor(v1), v2_Class_Monitor = Find_Class_Monitor(v2);
	//找到两个集合的代表元素
	if (v1_Class_Monitor != v2_Class_Monitor)//两个代表元素不相同,两个元素不在同一个集合,合并集合
		Classmate[v1_Class_Monitor] = v2_Class_Monitor;
}

int main()
{
	scanf_s("%d %d", &vnum, &arcnum);//输入元素数量和元素关系数量
	for (int i = 1; i <= vnum; i++)//初始化,还未合并集合,每个集合只有一个元素,每个人都是代表元素
		Classmate[i] = -1;
	int v1, v2;//两个元素
	for (int i = 1; i <= arcnum; i++)//并集
	{
		scanf_s("%d %d", &v1, &v2);
		AddClass(v1, v2);
	}

	//以下为测试代码
	//for (int i = 1; i <= vnum; i++)//找到班长
	//	printf("%d ", Find_Class_Monitor(Classmate[i]));
}

代码说明:如果问到了班长,班长直接到你这里来告诉你他是班长。那么我们查集的代码可以直接使用循环

int Find_Class_Monitor(int mate)//循环查集
{
	while (Classmate[mate] != mate)
		mate = Classmate[mate];
	return mate;//返回班长
}

当同学们进行了很多并查集的工作之后,同学们感觉这样太繁琐了,每次进行并查集,同学们都要去问一遍。同学们小脑瓜一开动,诶,我们只进行并查集操作,那我们只记住班长是谁不就行了。于是每次进行并查集操作时,每个询问过的同学都会把班长的名字记录下来。由于让询问过的同学记录下来班长的名字需要同学们口口相传,所以查集只能用递归,不能用循环。
这就是路径压缩。
在这里插入图片描述
但是当班级合并时你们班的班长换人了,但是你不知道,所以你还得要去问你的原班长以得知现班长是谁。
所以查集的代码不能改为直接找原班长,而是要问原班长现班长是谁。

于是查集的代码改为:

int Find_Class_Monitor(int mate)//查集
{
	if (-1 == Classmate[mate])//找到了班长
		return mate;
	return Classmate[mate] = Find_Class_Monitor(Classmate[mate]);//没找到班长就继续问,最后返回班长的名字给路径上询问的同学
}

推荐免费课程:浙江大学-数据结构-集合的表示及查集
浙江大学-数据结构-集合的并运算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值