13-并查集

数据结构并查集常用于将两个集合并起来以及查询两个元素是否隶属于同一个集合。相对于传统我们的求法,并查集算法极大减少了查询的工作量,提高了效率。

合并集合

假设我们有两个集合,常规情况下合并两个集合就是将它们混合起来:

但是在计算机中,如果我们想要合并两个集合,那么我们应该怎么做呢?这里合并集的数据结构提供了一种方法:将集合里的元素先按树状结构存储好,然后再在每一个集合中找到树根元素,将树根和另一个树根连起来就可以将两个集合合并成一个集合。下面就是两个集合未合并前按照树状结构排好的样子:

蓝色表示大集合1,粉色表示大集合2,现在我们想要将两个集合合成一个集合,只需要将粉色集合根元素连到蓝色集合根元素或者将蓝色集合根元素连到粉色集合根元素(粉色集合作为蓝色集合的分支或者蓝色集合作为粉色集合的分支)即可:

如果具体落实到代码,在形成两个大集合之前首先得有每一个小集合(大集合对应粉色集合/蓝色集合,小集合代表单独或多个粉色/蓝色元素形成的元素)。为了将集合连接起来,首先我们需要创建一个数组pre,这个数组里的每一个元素将会负责存储该元素的上级的位置,并且初始时需要对该数组进行初始化,初始化时每个元素都是一个集合,所以它们的pre数组存储的都是本身的下标。

#include<iostream>

using namespace std;

const int N = 100;
int pre[10];

void init()
{
	//初始化
	for (int i = 0; i < 10; i++)
	{
		pre[i] = i;
	}
}

下图1为初始状态,每个字母元素都代表自己一个集合,此时pre数组存储内容如下:

如果分别将上面的小集合合并成两个大集合,那么合并后的集合假设是下面的样子:

 此时pre数组下标将变成:

pre数组里面存储的就是每个元素的上级的坐标。而合并两个集合就是将一个元素例如合并A和B,那么我们只需要将A的上级指定为B,即令pre[4]=2。由此我们可以推出,合并两个集合的抽象操作具体到函数Com就对应就着修改pre数组的操作:

void Com(int x, int y)
{
	pre[find(x)] = find(y);
}

这里的find函数我们先按下不表,我们只需要知道它的意义是找到当前集合的根元素的下标就可以了,所以Com函数的操作就相当于将一个集合的根元素连接到另一个集合上的某一个元素中(常连到另一个集合的根元素上):

查询元素

上面我们接触到了find函数,这个函数就是用于查找某个元素A是否属于某个集合B,查找的过程就是根据要比较的元素A找到该元素所属的集合A的根元素C,再和集合B的根元素D进行对比,如果C和D是同一个元素,那么它们就来自同一个集合,反之则不是。所以查询元素的过程就是在树状结构中不断往上找根元素的过程,我们知道pre数组存储的是某个元素的上级的下标,那我们只需要不断循环往上找,当找到那个下标就是它本身那就代表它是根元素。对到代码就是:

int find(int x)
{
    while(x!=pre[x])
        x=pre[x];
	if (pre[x] == x) return x;
}

这里pre[X]==X就是找到了根元素,返回根元素的下标,要是找到的不是根元素,那么我们就让x等于它的上级下标,即继续向上找。这里我们可以优化一下,既然每次都向上层层找根元素这么麻烦,那么我们为什么不一步到位直接将要找的元素和根元素连在一起,这样就可以直接避免中间又要一步步找其它上级的过程:

将这个过程对应到代码就是使用一个递归过程,让每一次x寻找上级都变成寻找根即可:

int find(int x)
{
	if (pre[x] == x) return x;
	else return pre[x] = find(pre[x]);
}

参考资料:

参考资料1

猜你喜欢

转载自blog.csdn.net/m0_61151031/article/details/128908465