数据结构之并查集
一、定义
并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。
注:每棵树表示一个集合,节点表示集合中的元素
二、基本实现
并查集的最基本的三种操作,一个是初始化,一个是查询,一个是合并。
-
查询:找到元素所处集合的根节点。
-
合并:将两个元素所处的集合进行合并,其实质是将其中一个元素所处集合的根节点的父节点设为另一个元素所处集合的根节点。
下面是一个简单并查集的实现:
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
class Uls{
/* pa[x] = a 表示x的父节点为a */
public:
// 初始化集合:把每个单独节点的父亲设为本身(所处集合只有本身) pa[x] = x 代表x的父亲就是x
explicit Uls(size_t size);
// 查询操作:利用递归查询出x所处集合的根节点 (或者是集合的代表元素)
size_t find(size_t x);
// 合并两个元素所处的集合
void Union(size_t x ,size_t y);
private:
vector<size_t> pa;
};
Uls::Uls(size_t size):pa(size){
// 从0开递增初始化pa
iota(pa.begin(),pa.end(),0);
}
size_t Uls::find(size_t x) {
// 找到所处集合的根节点
return pa[x] == x ? x : find(pa[x]);
}
void Uls::Union(size_t x, size_t y) {
// 把x所在集合的根节点的父亲设为y所在集合的根节点 这样两个元素所在的集合就合并了
pa[find(x)] = find(y);
}
三、改进
1、路径压缩
上述查询的方式意味着每查询一次都要进行一次递归,实际上位于同一集合的元素只需要查询一次,因为它们所处同一个集合,拥有同一个根节点。所以该操作能采取路径压缩的方法:即查询过程中经过的节点都属于该集合,我们可以将其直接连接到根节点。
路径压缩后查询函数实现如下:
size_t Uls::find(size_t x) {
// 重点是pa[x] = find(pa[x]) 递归每经过一个节点就将该节点的父节点直接设为根节点 以便后续的查询
return pa[x] == x ? x : pa[x] = find(pa[x]);
}
2.启发式合并
合并时,选择哪棵树的根节点作为新树的根节点会影响未来操作的复杂度。我们可以将节点较少或深度较小的树连到另一棵,以免发生退化。
那节点数合并的参考实现:
struct Uls {
vector<size_t> pa, size;
explicit Uls(size_t size_) : pa(size_), size(size_, 1) {
iota(pa.begin(), pa.end(), 0);
}
void unite(size_t x, size_t y) {
size_t x_root = find(x);
size_t y_root = find(y);
if (x == y) return;
if (size[x_root] < size[y_root]) swap(x_root, y_root);
pa[y_root] = x_root;
size[x_root] += size[y_root];
}
};
参考:并查集 - OI Wiki (oi-wiki.org)