【数据结构高阶】第十一篇——并查集(原理+实现+应用)

⭐️ 今天要和大家介绍一个新的数据结构——并查集。听名字好像是把集合合并再查找元素,其实总体来说也是这样的,下面我们来和大家好好聊一聊这个玩意~
⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/data-structure


🌏概念和原理

并查集: 在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。并查集其实就是一个森林,由多个集合合并而成。

举例说明:
假设有10个人,给他们编号,依次是0-9,他们之间互相不认识,也就是说每个人都是一个独立的集合,用并查集表示就是如下图所示(其中每个成员是下标,内容为负数,代表该集合元素个数是该负数的绝对值,为正数,代表该下标的双亲是该正数。例如:对应的内容就是-1就是该小集体中元素个数为1,也就是自己一个元素):
在这里插入图片描述
现在,编号为0,3,4这三个人结识称为朋友,构成了一个朋友圈,也就是三个人形成一个集合,合并过程如下图:
在这里插入图片描述

下面是集合的树形表示:
在这里插入图片描述
接下来,编号为2,5,7,8成为一个朋友圈,编号为1,6,9成为一个朋友圈,合并结果如下:
在这里插入图片描述
用树形结构表示如下:
在这里插入图片描述
最后再操作一次,如果3和6结识称为好朋友,那么这两个朋友圈就要合并。合并分为两个步骤:

  1. 分别找到自己所在集合的根
  2. 把两个根按照上面的方法合并

树形图形式演示:
在这里插入图片描述
数组形式演示:
在这里插入图片描述

总结:

  1. 数组的下标对应集合中元素的编号
  2. 数组中如果为负数,负号代表根,数字的绝对值代表该集合中元素个数
  3. 数组中如果为非负数,代表该元素双亲在数组中的下标

🌏实现

🌲整体框架

总体来说,实现起来还是比较简单的,底层我们选择用vector来实现,构造函数将vector的内容都初始化为-1,框架如下:

class UnionFindSet
{
public:
	UnionFindSet(size_t size)
		:_ufs(size, -1)
	{}
private:
	vector<int> _ufs;
};

🌲查找元素属于哪个集合

步骤:

  1. 当元素对应的下标对应的内容为正数时(也就是该元素不为根),我们需要更新该下标,也就是index=_ufs[index],让自己成为双亲
  2. 继续判断,如果对应内容为负数,说明该下标是根,直接返回

代码实现如下:

int FindRoot(int x)
{
	assert(x >= 0 && x < _ufs.size());

	int index = x;
	while (_ufs[index] >= 0)
	{
		// 更新双亲
		index = _ufs[index];
	}
	return index;
}

🌲合并两个集合

两个步骤:

  1. 先分别找到两个集合对应的根
  2. 然后合并两个根,让其中一个根归并到一个根的下面,更新新的双亲的内容和被并入的根的内容(把内容修改为新根的下标)
    具体演示:
    在这里插入图片描述
    代码实现:
void Union(int x1, int x2)
{
	assert(x1 >= 0 && x1 < _ufs.size());
	assert(x2 >= 0 && x2 < _ufs.size());

	int root1 = FindRoot(x1);
	int root2 = FindRoot(x2);

	// 双亲不同就合并,否则不做处理
	if (root1 != root2)
	{
		_ufs[root1] += _ufs[root2];// 把root2合并到root1
		_ufs[root2] = root1;// 改变祖先
	}
}

🌲统计集合个数

很简单,遍历一遍数组,统计内容为负数的下标的个数,即统计了集合个数
代码实现如下:

int Size()
{
	int count = 0;
	for (auto e : _ufs)
	{
		if (e < 0)
			++count;
	}

	return count;
}

🌏应用

这里以LeetCode中的一道题为例,链接——省份数量

题目描述:

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。

实例:
在这里插入图片描述
思路:
这里就是用到并查集的知识,我们可以通过遍历二维数组得到每两个城市的关系,因为isConnected[i][j]和isConnected[j][i]的内容其实是一样的,所以我们这里只需要遍历右上角的一部分内容即可确定所有的两个城市的关系。
在这里插入图片描述

如果两个城市有关系,那么就合并,没有就不合并,最后返回集合个数就是身份数。
解题代码如下:

class Solution {
public:
    int findCircleNum(vector<vector<int>>& isConnected) {
        // if (isConnected.size() == 1) return 1;
        vector<int> ufs(isConnected.size(), -1);
        for (size_t i = 0; i < isConnected.size(); ++i)
        {
            for (size_t j = i + 1; j < isConnected.size(); ++j)
            {
                // 为1j就合并
                if (isConnected[i][j]) Union(ufs, i, j);
            }
        }

        int count = 0;
        for (auto& e : ufs)
        {
            if (e < 0) count++;
        }

        return count;
    }
    int FIndRoot(vector<int>& ufs, int x)
    {
        int index = x;
        while (ufs[index] >= 0)
        {
            index = ufs[index];
        }
        return index;
    }

    // 合并两个集合
    void Union(vector<int>& ufs, int x1, int x2)
    {
        int root1 = FIndRoot(ufs, x1);
        int root2 = FIndRoot(ufs, x2);

        // 祖先不同就合并
        if (root1 != root2)
        {
            ufs[root1] += ufs[root2];// 把root2合并到root1
            ufs[root2] = root1;// 改变祖先
        }
    }
};

🌐总结

并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。应用的地方也比较多,查找的时间复杂度是每个集合的深度次,效率也很不错。今天的内容就到这里了,喜欢的话,欢迎点赞支持和关注~
在这里插入图片描述

  • 33
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 31
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呆呆兽学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值