并集:将两个集合并成一个集合
查集:查找某个元素属于哪个集合
并查集就是并集和查集两个操作。
先讲一个小例子,假如年级里每个班中,班里的每个同学只认识身边的几个同学,只知道班里有身边的几个同学,而不认识有其他同学。除了班长,其他同学一概不知道自己的班长是谁。
故事一:你想知道我们的是否在同一个班,你要怎么做?
你问你身边的同学:“我们的班长是谁呀?”那个同学说:“我也不知道呀,我要去问问我身边同学。”同学身边的同学说:“我也不知道啊?我也要问我身边的同学。”
如此循环,直到问到了班长, 班长告诉问他的同学:“你们的班长是我。”那个同学于是告诉了往回告诉了问他的同学。如此循环,直到告诉了你。这下你知道了你的班长是谁。但你没法判断我们的班长是否为同一个人?所以我也像你一样问出了我的班长是谁。这下我们就知道我们的班长是否为同一个人,于是我们可以判断我们是否在同一个班了。这就是查集。
故事二:你与我不知道我们是否在同一个班。但是如果我们在不同的班,我们想要让两个班的班长把两个班合并起来,组成一个更大的班,并让我的班长继续做班长,你的班长做普通同学。我们要怎么做?
我们通过刚才询问的方式找到了各自的班长:
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]);//没找到班长就继续问,最后返回班长的名字给路径上询问的同学
}