目录
并查集的原理
在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集,然后按一定的规律将它们归于同一组元素集合来合并它们。在此过程中要反复用到查询某一个元素归属于这个集合的运算。适合于描述这类问题的抽象数据类型称为并查集(union-find
set)。
并查集就是森林,什么是森林?森林就是由多棵树构成的集合,那么在一个数据结构中怎么有多棵树呢?
比如说某个公司今年校招发,分别在A,B,C三个地方招了10人,那么这三个地方的人因为老家是一个地方,分别建立了三个群,变成了三个不同的朋友圈。那么我们怎么表示这三个朋友圈呢?并查集就是将这一堆数据分出不同的集合。并查集还有一个特点,当两个集合有交集的时候,两个集合会合并。
【question1】
最开始我们需要给这10个同学一个编号,编号为0-9,我们给到编号之后,按这个编号来分就可以了。但是如果我们给的这是个同学都是它的名字,那么我们怎么给它们分配编号呢?这样的问题该怎么解决?
当我们拿到对应的编号,无论是字符串还是其它,我们将它建立好对应的映射关系。
我们来实现一下。当传入的参数给到一个数组,我们需要将它与编号一一建立映射关系,比如说数组给了十个名字,我们借助两个数据结构,分别是vector和map。使用vector直接把这些值存起来就可以了,当遇到第一个名字,那么直接将它放到数组0下标的位置,遇到第二个名字,将它放到数组下标为1的位置。这样建立了映射,使用编号就可以找到对应人的名字。
当每个人都有一个编号的时候,可以通过编号找到人,那另一方面,如果要通过人找编号,如果不考虑效率,直接通过编号暴力寻找,依次遍历,时间复杂度为O(N),这样是可以找到的。如果考虑效率,我们可以使用map,建立一个映射关系,通过人找编号,传入的人的类型是我们的模板T,没有实例化的时候还不知道是什么类型,和我们的int类型的编号进行映射map<T,int> _IndexMap;就可以很好的解决这个问题。图的问题也是用这种方法来解决。
所以传入数组之后,依次遍历数组,将数组元素push_back加入到vector中,编号和人建立映射,可以通过编号找人。然后通过map,operator[],将人的名字和编号建立映射,通过人的名字找编号。
UnionFind.h
#include <vector>
#include <unordered_map>
template<class T>
class UnionFindSet
{
public:
UnionFindSet(const T* a,int n)
{
for (size_t i = 0; i < n; i++)//这里的n不能用sizeof(a),因为数组传参会退化,退化为指针,sizeof算出的是指针的大小,32位下是4byte。
{
_a.push_back(a[i]);
_indexMap[a[i]] = i;
}
}
private:
vector<T> _a; //编号找人
unordered_map<T, int> _indexMap;//人找编号
};

建立映射成功,这样我们就可以用编号控制。
实际当中不一定这样给,如果给的是人名,像上面的例子,就可以用这种方法。
并查集的实现
并查集的基本结构
上面的问题是用来解决怎样分配编号的,继续回到上面的问题,怎么将一组的同学分配到一块,如何去表示这三个集合呢?
我们将它分配为三颗树即可。在这个组当中随便选一个编号,其它成员去做这个选出来编号的孩子。我们统一一下,选编号最小的去做根。
这样的方式有几个特点:
- 类似堆一样用下标表示关系
- 用了双亲表示法,这里需要找自己的父亲

最开始都存-1,不给-1也可以,-2,-3都可以,但是-1最好。表示最开始它们各自就是一个集合,有十个小集合,这里0,6,7,8是一个集合,选出一个最小编号0作为根,678编号存储的是父亲的编号,当遇到一个自己集合的编号,就将编号对应的数组值加到根上。举个例子,0跟6成为朋友,将6的-1加到0的-1上,然后6再存0的下标,遇到7,将7的-1加到0对应数种。
所以如果vec
本文详细介绍了C++中并查集的概念、原理及其实现。并查集是一种用于处理一些不相交集合的合并与查询的问题的数据结构。文中讨论了如何建立编号与人名的映射关系,以及并查集的基本结构,包括使用vector和map存储数据。接着,文章通过示例解释了如何合并集合以及查找根节点的方法。并查集的应用场景包括计算省份数量和判断等式方程的可满足性。最后,文章提及了两种扩展优化:路径压缩和优先合并小集合到大集合。
最低0.47元/天 解锁文章
2778

被折叠的 条评论
为什么被折叠?



