文章目录
1.并查集的定义
并查集是一个多棵树的集合(森林)。
并查集由多个集合构成,每一个集合就是一颗树。
并:合并多个集合。查:判断两个值是否在一个集合中。
- 存储结构:数组
- 初始化:数组元素全部初始化为
-1
。 - 存储数据:根节点为负数,其绝对值为集合中元素的个数,孩子结点中存放父节点的下标。
eg:
一共10个人
0 1 2 3 4 5 6 7 8 9
开始时这10个人各为一个集合,没有父节点,这里将数组全部设置为-1。
之后,将10个人分成3组,每组以树的形式存储,树的根节点可以任意取。
这里假设0 4 5 一组,1 3 6 一组,2 7 8 9 一组。
使用双亲表示法:根节点没合并一个,对应数组的值减1,最终合并结束后,对应的绝对值就是节点的个数。
之后假设一组和二组合并,只需要更新数组的对应的值即可。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class UnionFindSet
{
public:
// 默认全部都无父节点,为-1
UnionFindSet(size_t sz)
:_ufs(sz, -1)
{}
// 合并两个集合
bool Union(int x, int y)
{
int xroot = FindRoot(x);
int yroot = FindRoot(y);
// x,y为同一个集合的话合并失败
if (xroot == yroot)
return false;
// 优化:数据量小的结点并入数据量大的结点
// 代码中的优化可要可不要,对于效率影响不大。
if (abs(_ufs[xroot]) < abs(_ufs[yroot]))
swap(xroot, yroot);
// 默认并入xroot,要保证xroot的数据量大
_ufs[xroot] += _ufs[yroot];
_ufs[yroot] = xroot;
return true;
}
// 核心代码:查找一个子集的根
int FindRoot(int x)
{
int root = x;
// 查找到结点权值为负的根
while (_ufs[root] >= 0)
{
root = _ufs[root];
}
// 如果树的层数太高,我们就无法保证O(1)的时间复杂度
// 优化:将被查找结点及以上的结点直接连接到根
int cur = x;
while (_ufs[cur] >= 0)
{
int parent = _ufs[cur];
_ufs[cur] = root;
cur = parent;
}
return root;
//重复迭代的代码太长,我们也可以使用递归实现,两行代码完成查找+路径优化:
//if (_ufs[x] >= 0) _ufs[x] = FindRoot(_ufs[x]);
//return _ufs[x] < 0? x : _ufs[x];
}
// 获取有多少个集合
int SetCount()
{
int cnt = 0;
for (int e : _ufs)
{
if (e < 0)
cnt++;
}
return cnt;
}
// 查询两个元素是否在同一个集合中
bool IsInSet(int x, int y)
{
return FindRoot(x) == FindRoot(y);
}
private:
vector<int> _ufs; // 根节点为负数,其绝对值为集合中元素的个数,孩子结点中存放父节点的序号
};