本人实力不济,如有错误或建议及补充,请指出
1.定义
并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。
顾名思义,并查集支持两种操作:
- 合并(Union):合并两个元素所属集合(合并对应的树)
- 查询(Find):查询某个元素所属集合(查询对应的树的根节点),这可以用于判断两个元素是否属于同一集合
并查集在经过修改后可以支持单个元素的删除、移动。
(注:这两段文字均源于OI wiki
2.实现
我们首先设置一个数组fa代表i的父亲是fa[i]
2.0 初始化
我们假设每个节点的父亲刚开始都是自己,即:
int fa[N];
void init(){
for(int i=1;i<=n;i++) fa[i]=i;
}
2.1 查询自己的祖先(find函数的实现)
2.1.1 朴素实现
顾名思义,X先查询自己的父亲Y,Y再查询自己的父亲是Z……直到最高的一辈,即F查询到自己的父亲是F。
此时F就是X的祖先。
int find(int x)
{
if(fa[x]!=x) return find(fa[x]);//不是祖先,找它的父亲
else return x;//找到了祖先,返回
}
2.1.2 路径压缩
但考虑极端情况,朴素算法的并查集构成的树是一条链式的,时间复杂度是O(n)。
再回归定义,我们只需要该树的根节点就够了,所以此时可以考虑路径压缩
我们可以把每个结点直接连接到根节点。
代码实现:
int find(int x)
{
if(fa[x]!=x) return fa[x]=find(fa[x]);//去找祖先,将找到的祖先设为自己的父亲(辈分乱了啊喂)
else return x;
}
除了第一次查询时间复杂度为O(n),后续几次查询均为O(1)
(谁家好人费怎么大劲造个并查集结果只查询一次)
2.2 合并集合(Union函数)
为什么不将函数名取为union呢,因为union是C++中的联合体(一种关键字就是了)。
由于路径压缩已经介绍了,这里就不提供朴素实现了
void find(int x,int y)
{
int fax=find(x),fay=find(y);//查询X,Y的祖先
fa[fay]=fax;//将Y的祖先的父亲设为X的祖先
}
3.拓展阅读
- [并查集 - OI Wiki]