文章目录
并查集
前言
数据结构与算法是每一个CS人的必经之路
路漫漫其修远兮,今天总结的是关于数据结构的并查集
一、并查集是什么?
并查集(Disjoint-Set)是一种可以动态维护若干个不重叠的集合,并支持合并于查询的数据结构。
它被众多OIer认为是最简洁而优雅的数据结构之一,主要用于解决一些元素分组的问题。
用一个形象的比喻,并查集就像是一个老大,带着一堆小弟,并且不断招收新的小弟。这种”拉帮结派“的比喻就是并查集最核心的思想”代表元“。即每个集合选择一个固定的元素作为整个集合的”代表“,这是集合的表示方法,除此之外,我们还需要定义归属关系的表示方法。
->定义集合表示
->定义归属关系(如何维护)
二、原理及实现
1.如何定义
思路1. 维护一个数组f,用f[x]保存元素x所在集合的代表。
这种方法可以达到O(1)的查询速度,但是在合并时需要大量修改元素的 f 值,效率低下
思路2. 使用树形结构储存每个集合。
在一棵树中,树的节点就可以充当集合中的元素,树的根即为集合元素的代表。所以并查集实际是一片森林。
我们可以通过维护数组fa来记录这片森林(多个集合),fa[x]保存x的父节点,特别的树根的父节点为他本身(代表)
2.代码实现
并查集包括如下两个操作:
1.Get 查询一个元素属于哪个集合
2.Merge 把两个集合合并
代码如下:
int fa[N];
inline void init(int n){//初始化操作
for(int i=1;i<=n;i++) fa[i]=i;
//将每个元素的代表先设置为自己,为之后合并做初始化
}
inline int find(int x){
if(fa[x]==x) return x;
else return find(fa[x]);
//我们用递归的写法实现对代表元素的查询
//一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。
//要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。
}
inline void merge(int x,int y){
fa[find(x)]=find(y);
//最易理解的,找到两个集合的代表元素,然后将前者的父节点设为后者即可
}
3.路径压缩与按秩合并
我们知道如果直接保存fa[x]保存代表,在查询时有很高的效率
实际上我们只关心每个集合对应的树形结构的根节点是什么,所以我们想尽可能减少树的高度,以达到更高的查询效率,即我们不关心树长什么样,最好情况是一个根节点连着无数的子节点,而子节点没有子子节点,例如下面两种图,在并查集中是等价的,但是右树的查询x的效率明显高于左树
所以我们可以在每次执行get操作时都把访问过的节点直接指向树根。这种优化方式被称为路径压缩
代码如下:
inline int get(int x){
if(fa[x]==x) return x;
return fa[x]=get(fa[x]);//路径压缩,fa直接赋值为代表元素
}
采用路径压缩的并查集,每次get查询的均摊复杂度为O(logN)
还有一种优化方法叫做按秩合并
对于秩有两种定义,一时某集合代表树的深度,二时该集合的大小。
但无论如何,该操作都是想实现--让小的集合直接记录在大集合的代表元上,即把小树直接插在大树的树根上,来实现最终查询尽可能快的结果
总结
对于并查集还有很多的拓展,诸如
”拓展域“与”边带权“的并查集
最小生成树下的并查集作用
在此暂不作整理,后续在学习最小生成树或图论更深层学习中再做强调