并查集的原理及实现

目录

0.引例

1.并查集的原理

2.并查集的存储结构

3.并查集的实现

并查集接口总览

构造函数

查找元素属于哪个集合

判断是否属于同一个集合

合并两个集合

集合的个数

4.并查集完整代码附录

5.并查集在OJ中的应用

省份数量

等式方程的可满足性

0.引例

在我们刚上大学的时候,假如一个宿舍10个人,分别来自广东、湖南、江西;一开始谁也不认识谁,于是广东的只和广东的一起玩,湖南的只和湖南的一起玩,江西的只和江西的一起玩,毕竟是老乡嘛~ ,于是一个宿舍整体被划分成了三个小团体。

为了表示方便,我们给10个人编号,从0开始,于是10个人的编号依次为 0~9:

于是,我们可以很直观的看出谁来自于哪里。 

后来呢,湖南的小伙伴和江西的小伙伴因为共同的口味,都喜欢吃辣的东西,玩在了一块,于是这两个小集体组成了一个大的集体。

我们可以这样划分:

于是,我们可以简单直观的看出爱吃辣的有哪些同学,不爱吃辣的有哪些同学。

当我们划分好了集合之后,就能很方便的进行元素和集合之间、集合和集合之间的操作了,适合进行这些操作的数据结构就是并查集

1.并查集的原理

原理:将n个不同的元素划分成一些不相交的集合,开始时,每个元素自成一个单元素集合,然后按一定的规律将属于同一组元素的集合合并;在此过程中要反复用到查询某一个元素归属于那个集合的运算。(适合描述这种问题的抽象数据结构就叫做 —— 并查集

简单来说,并查集就是能够快速判断一个元素位于哪个集合中,并且能够根据指定条件将不同的集合进行合并的这么一种数据结构。比如上述引例中,根据地域将10个单集合合并成了三个集合,根据是否爱吃辣,把湖南和江西的同学合并成了一个集合。反之,划分好集合之后,我们也可以快速判断一个同学是否爱吃辣?来自哪里?

2.并查集的存储结构

要想实现一个并查集,首先需要确定其底层存储数据的结构,我们使用顺序表,也就是数组来存储。并查集的底层结构和堆类似,用下标表示当前结点和双亲结点之间的关系(重要)。

使用数组存储的理由如下:

  • 使用并查集的时候需要对每个元素从0开始编号,而数组天然就是从0开始编号的结构。
  • 数组支持随机访问,查找效率高。

数组各字段的含义:

  1. 数组的下标对应集合中元素的编号
  2. 数组中如果为负数,负号代表根,数字代表该集合中元素个数(一般选择编号最小的为根)
  3. 数组中如果为非负数,代表该元素双亲在数组中的下标

以引例为例说明一下:

  • 一开始,为每个元素从0开始编号,并且每个元素都是一个集合,并且都代表该集合的根。 

  •  把引例中的集合转化为并查集的形式

  • 用树形结构表示一下各个集合,更加直观

3.并查集的实现

并查集接口总览

首先需要明确的是,使用并查集的时候,我们为每个要存储的数据元素都进行了编号(从0开始),因此,我们在操作并查集的时候,主要通过编号来进行,也就是数组的下标

class UnionFindSet
{
public:
	// 构造函数
	UnionFindSet(size_t n)
		:_ufs(n, -1)
	{}

	// 查找一个元素所在的集合
	int FindRoot(int x);

	// 判断两个元素是否在同一个集合中
	bool InSet(int x1, int x2);

	// 合并两个元素所在的集合
	void Union(int x1, int x2);

	// 获取集合的个数 注意:不是元素个数
	size_t SetSize();

private:
	vector<int> _ufs;
};

并查集中最重要的接口就是 合并两个集合Union 和 判断两个元素是否在同一个集合中InSet,这两个函数都需要借助 查找根FindRoot来进行,最后还有一个获取集合的个数SetSize。

构造函数

构造函数只需要将并查集中的所有元素值设置为-1即可,表示当前每个元素都是一个集合,集合中有一个元素。

class UnionFindSet
{
public:
    // 构造函数:指定并查集的大小,将并查集中的元素值全部设置为-1
    UnionFindSet(size_t n)
	    :_ufs(n, -1)
    {}

private:
    vector<int> _set;
};

查找元素属于哪个集合

对于并查集中的每个元素来说,我们需要标识元素属于哪个集合,我们采用集合中编号最小的元素为集合的根,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值