c++并查集

什么是并查集? 逐字拆解一下,并、查、集。这个三个字,其中前面两个字都是动词,第三个字是个名词。

先看名词,因为只有知道了这个东西是什么,才能去理解它能干什么。

集就是集合,中学时期就学过这个东西,集合用大白话说就是将一堆元素没有顺序地摆放在同一个地方。

其实并查集本质就是集合。

那它能做什么呢?这就要看前两个字 - “并” 和 “查”。

集合的一些操作,例如,交集,并集等等,这里的 “并” 其实就指的是并集操作,两个集合合并后就会变成一个集合。例如:

{1,3,5,7} U {2,4,6,8} = {1,2,3,4,5,6,7,8}

那 “查” 又是什么呢?集合本身只是容器,最终还是要知道里面存的是什么元素,因此这里的 “查” 是对于集合中存放的元素来说的,即要查找这个元素在不在集合中,还要确定这个元素是在哪个集合中。

好了,现在知道并查集是什么,以及它能干什么了,总结下来就是:

并查集可以进行集合合并的操作(并) 并查集可以查找元素在哪个集合中(查) 并查集维护的是一堆集合(集) 举个例子:

有8个元素:14, 35, 48, 87, 65, 20。

集:把个位相同的数字归在同一个集合。集合划分为如下:

{14}, {35, 65}, {48}, {87}, {20}

查:给定一个元素,得到这个元素属于哪个集合。例如:

给定元素14,可以得出:14 位于第一个集合。 给定元素65,可以得出:65 位于第二个集合。 给定元素20,可以得出:20 位于第五个集合。 并:将两个集合合并,例如将个位为偶数的集合合并的到第一个集合,个位为奇数的集合合并到第二个集合,结果如下: {14, 48, 20}, {35, 65, 87}

相信通过上面的表述,已经知道,并查集维护的是一堆集合。用什么样的数据结构表示并查集?

对于并查集,有两个信息是必须要知道的:

元素的值。 集合的标号。 一个元素必须仅存在于一个集合中,一个集合中可以有多个元素。

元素对集合来说,是多对一的关系。这么看来可以用一个健值对的结构来表示并查集(键是元素,值时所属集合)。

但是如果对元素本身没有特定要求的话,可以使用数组,这样速度更快,使用起来也更加简单:

{0}, {1}, {2}, {3}, {4}, {5} => [0,1,2,3,4,5]

{0,1,2}, {3,4,5} => [0,0,0,3,3,3] or [1,1,1,4,4,4] 在解释上面的数组表示方式之前,不知道有没有发现一个事实:

元素本身的值是固定不变的,但是元素所属的集合是可以变化的” 因此可以使用两个数组:

第一个数组保存所有元素 第二个数组使用数组的 下标 来代表数组一中元素,对应位置上 存放的值 表示元素所属的集合。 例如:{0,1,2}, {3,4,5} => [0,0,0,3,3,3]:第一个数组是0, 1, 2, 3, 4, 5,第二个数组是0, 0, 0, 3, 3, 3。第一个数组保存了所有元素,第二个数组保存了元素所属集合。

第二个数组中,第一个元素是0,含义是:第一个数组的第一个元素属于 0 号集合。 第二个数组中,第二个元素是0,含义是:第一个数组的第二个元素属于 0 号集合。 第二个数组中,第三个元素是0,含义是:第一个数组的第三个元素属于 0 号集合。 第二个数组中,第四个元素是3,含义是:第一个数组的第四个元素属于 3 号集合。 第二个数组中,第五个元素是3,含义是:第一个数组的第五个元素属于 3 号集合。 第二个数组中,第六个元素是3,含义是:第一个数组的第六个元素属于 3 号集合。 说完了集合的表示,来看看如何基于这种表示去实现 “并” 和 “查”,也就是集合的合并和元素的查找,这两个操作是相互影响的。合并其实就是改变第二个数组中存放的值,这个值表示的是第一个数组对应位置元素所在的集合。

上述实现并查集方法很直观。但是将连个集合合并的时候,需要修改其中一个集合中的所有元素对应的数组二中的值,有没有办法优化下呢?

这个问题的源是:第二个数组中保存的是第一个数组中各个元素所属集合,所以合并集合的时候,第二个数组中需要修改元素比较多。

可以为每个元素选出一个代表它的元素,数组二中存放代表元素

例如:{0,1,2}, {3,4,5} => [0,0,0,3,3,3]:第一个数组是0, 1, 2, 3, 4, 5,第二个数组是0, 0, 0, 3, 3, 3。第一个数组保存了所有元素,第二个数组保存了能代表该元素的元素。

第二个数组中,0号位置保存的是0,含义是:第一个数组的0号位置保存的元素和 第一个数组中的0号位置保存的元素属于同一个集合。

第二个数组中,1号位置保存的是0,含义是:第一个数组的1号位置保存的元素和 第一个数组中的0号位置保存的元素属于同一个集合。

第二个数组中,2号位置保存的是0,含义是:第一个数组的2号位置保存的元素和 第一个数组中的0号位置保存的元素属于同一个集合。

第二个数组中,3号位置保存的是3,含义是:第一个数组的3号位置保存的元素和 第一个数组中的3号位置保存的元素属于同一个集合。

第二个数组中,4号位置保存的是3,含义是:第一个数组的4号位置保存的元素和 第一个数组中的3号位置保存的元素属于同一个集合。

第二个数组中,5号位置保存的是3,含义是:第一个数组的5号位置保存的元素和 第一个数组中的3号位置保存的元素属于同一个集合。

这个时候,如果要合并两个集合,只需要修改代表元素即可。

例如:将{0,1,2}, {3,4,5} => [0,0,0,3,3,3]中的第二个集合合并到第一个集合中,。只需要修改第二个集合的代表元素集合,合并后为:{0,1,2,3,4,5} => [0,0,0,0,3,3]

这个时候,问:5这个元素位于哪个集合?查找过程如下:

在数组一中找到 5 这个元素的位置下标是:5 在第二个数组中查看下标5位置保存的元素是3。说明5这个元素和3这个元素在同一个集合。 在数组一中找到 3 这个元素的位置下标是:3。在第二个数组中查看下标3位置保存的元素是0。说明5这个元素和0这个元素在同一个集合。 在数组一中找到 0 这个元素的位置下标是:0。在第二个数组中查看下标0位置保存的元素是0,也就是找到了代表元素。得出结论5这个元素的代表元素是0,他们在同一个集合。 第二个数组保存代表元素,就能简化集合的合并。


总结:

用一个数组保存对应位置元素所属集合的代表元素。 AB两个集合合并:将B集合代表元素的代表元素设置为A集合的代表元素。 查找C元素属于哪个集合:找C元素的代表元素,如果不是他自己,就重复查找代表元素的代表元素,知道查找到一个元素的代表元素是它自己,C就属于整个代表元素所代表的集合。


代码实现:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

int p[N];//存放代表元素

//查找 x 所属的集合,就是 x 元素的代表元素
int find(int x)
{
    //如果 x 的代表元素不是他自己,就递归的x的代表元素修改为代表元素的代表元素
    if(x != p[x]) p[x] = find(p[x]);

    return p[x];
}

//合并a b所在的两个集合
void merge(int a, int b)
{
    int pa = find(a);//找到 a 所在集合的代表元素
    int pb = find(b);//找到 b 所在集合的代表元素
    if(pa != pb)//如果不是同一个,则属于不同集合,需要合并
    {
        p[pa] = pb;//将a所在集合代表元素的代表元素设置为b所在集合的代表元素。
    }
}


void query(int a,int b)
{
    int pa = find(a);//找到 a 所在集合的代表元素
    int pb = find(b);//找到 b 所在集合的代表元素
    //判断 a 和 b 是否有同一个代表元素
    if(pa == pb) cout << "Yes" << endl;
    else cout << "No" << endl;
    return ;
}
int main()
{
    int n, m;
    cin >> n >> m;
    //初始化代表元素集合,开始的时候,各自属于一个集合,即每个元素的代表元素是他自己。
    for (int i = 1; i <= n; i ++ )
        p[i] = i;

    while (m -- )
    {
        char op;
        cin >> op;
        int a, b;
        cin >> a >> b;

        if(op == 'M')
        {//合并
            merge(a, b);
        }
        else
        {//查询
            query(a, b);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值