定义
并查集(Disjoint-Set)是一种可以动态维护若干个不重叠的集合,并支持合并和查询的数据结构,详细的说,并查集基本包括两个操作
- Get 查询一个元素属于哪一个集合
- Merge 把两个集合合并为一个大集合
解释
相信大多数人看了上面的官方语言都满头雾水,其实为了实现这种数据结构,我们用一个固定的元素来代表某个集合,对于一些归属关系,我们则可以用一个树形结构来存储每个集合,树上的每个节点代表每个元素,树根就是这个集合的代表元素。
为便于理解,做个形象的比喻,将每个节点比作许多人,节点所属的集合根节点的代表元素就是它的祖先,为了区分这些人,我们只需要知道每个人的祖先是谁即可,不用知道他们之间的具体关系不必清楚,因为我们要做的只是区分这些人,也就是这些元素。
因此,我们在执行get操作时,把每个访问过的节点都直接指向根节点,这种优化方法被称为路径压缩
在基本代码中,我们通常会用到这种优化方法,下面是基本代码:
int find(int x)
{
if(x==fa[x]) return x;
return fa[x]=find(fa[x]);
}
其中fa[x]数组存储的是x节点的祖先
例题:
边带权
由于并查集的特殊结构,我们可以将路径压缩的并查集看做是一棵只有两层的树,我们可以在书中的每一条边上记录一个权值,维护一个数组val,用val[x]保存节点x到fa[x]两个节点之间的权值,在每次路径压缩时去更新访问过节点的val值,这就是所谓的边带权的并查集。
int find(int x)
{
if(x==fa[x]) return x;
int root=find(fa[x]);
val[x]=max(val[fa[x]],val[x]);
return fa[x]=root;
}
例题:
扩展域
对于两两不同的节点,他们可能存在不同的关系,这时,使用只能传递单一关系的并查集似乎不太实用,我们可以向原先存储关系的fa数组向后延伸n位,前一部分表示朋友关系,后一部分则表示敌人关系
对于两个元素(x,y),若他们是朋友,则只需merge(x,y),若他们是敌人,则merge(x+n,y),merge(y+n,x)。其中merge表示关系的传递(将x的祖先认为是y的祖先)。
当然,在许多题目中,关系很有可能不止有一种两种,读者们随机应变,向后酌情延伸即可。