非常巧妙用来解决集合问题的数据结构--并查集

并查集

  • 并查集应用
  • 并查集原理
  • 并查集实现

1.并查集应用

在很多笔试题目中,经常需要将n个不同元素划分成不相交的集合通过一定的条件和关系让一些集合合并。在此过程中经常需要判断该元素是否属于某个集合,或查询该集合中元素的个数。适用于解决这类问题的数据结构类型称为并查集

题目1:.547. 朋友圈

题目2:990. 等式方程的可满足性

2.并查集原理

通过数组下标标识元素本身,数组内容标识其集合上层节点。举个例子

在班里有三波人是互相熟悉的,他们都有分别都有一个老大。现在给这些人进行编号{0,1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

用一个数组来存储表示集合,数组下标就是同学编号,数组中的数字代表这个集体有多少个人。(负号后面解释)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DHjp6cUg-1597506645935)(C:\Users\航航雷\AppData\Roaming\Typora\typora-user-images\image-20200815231656341.png)]

三个小团体分别是:team1={0,3,5} team2={1,4,7,9} team3={2,6,8,10}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sIEOBBpM-1597506715261)(C:\Users\航航雷\AppData\Roaming\Typora\typora-user-images\image-20200815231656341.png)]

小团队分好伙后就变成了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kKNKWNZF-1597506645937)(C:\Users\航航雷\AppData\Roaming\Typora\typora-user-images\image-20200815232556827.png)]

从上图可以观察到编号3,5属于team1,编号4,7,9属于team2,编号6,8,10属于team3。它们坐标数组中的值是其老大的坐标。

可以得到一下结论

  1. 数组的下标对应集合中元素的编号
  2. 数组中如果为负数,负号代表根,数字代表这个集合中的元素个数
  3. 数组中如果为非负数,代表该元素父节点在数组中的下标。

过了一阵时间,老大0和老大1相互认识了,因此也介绍它们的小弟相互认识,因此他们也就成了同一个集合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02RBf60O-1597506645939)(C:\Users\航航雷\AppData\Roaming\Typora\typora-user-images\image-20200815233229743.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7zNOrUSW-1597506645942)(C:\Users\航航雷\AppData\Roaming\Typora\typora-user-images\image-20200815233337553.png)]

现在0集合就有7个人,2集合就有4个人。变成了两个集合

通过这个例子就能了解到并查集能解决的问题。

  1. 查找元素属于哪个集合

    根据元素数组中数据,沿着一直查找到数据为负数的节点。

  2. 查看两个元素是否属于一个集合

    如果他们的根节点相同,他们就属于同一个集合

  3. 可以将两个集合合并成一个集合

    找到两个集合的根节点,将其中一个根节点名称改为另一个根节点。

    另一个根节点加上当前集合元素个数。

  4. 集合的个数

    遍历数组,查看数组元素为负数的个数,就是集合的个数。

3.并查集的实现

#include<iostream>
#include<vector>
using namespace std;
class UnionFindSet
{
private:
    vector<int> _ufs;
public:
    //构造函数
    UnionFindSet(int size)
        :_ufs(size, -1)
    {}

    //找到一个元素所在根节点
    int FindRoot(int index)
    {
        while (_ufs[index] >= 0)
        {
            index = _ufs[index];
        }

        return index;
    }

    //将两个集合合并
    bool Union(int set1, int set2)
    {
        int root1 = FindRoot(set1);
        int root2 = FindRoot(set2);

        _ufs[root1] += _ufs[root2];
        _ufs[root2] = root1;
        return true;
    }

    //统计集合元素
    int Count() const
    {
        int count = 0;
        for (const auto& e : _ufs)
        {
            if (e < 0)
                ++count;
        }
        return count;
    }

    //遍历按序输出各集合元素
    void Print()
    {
        int num = Count();
        for (int i = 1; i <= num; i++)
        {
            int index = 0;
            for (int i = 0; i < num; i++)
            {
                int temp = FindRoot(index);
                int flag = 0;
                for (int j = index; j < _ufs.size(); j++)
                {
                    if (FindRoot(j) == temp)
                    {
                        cout << j << " ";
                    }
                    else if (flag == 0)
                    {
                        //保证从小到大输出
                        index = j;
                        flag = 1;
                    }

                }
                if (i != num)
                    cout << endl;
            }
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值