并查集2——带权并查集

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并查集2——带权并查集相关的知识,希望对你有一定的参考价值。

路径压缩

前面的并查集的复杂度实际上有些极端情况会很慢。比如树的结构正好是一条链,那么最坏情况下,每次查询的复杂度达到了 O(n)。

路径压缩 的思想是,我们只关心每个结点的父结点,而并不太关心树的真正的结构。

这样我们在一次查询的时候,可以把查询路径上的所有结点的 father[i] 都赋值成为根结点。只需要在我们之前的查询函数上面很小的改动。

 
int get(int x) {
    if (father[x] == x) { // x 结点就是根结点
        return x; 
    }
    return father[x] = get(father[x]); // 返回父结点的根结点,并另当前结点父结点直接为根结点
}

 

下面图片是路径压缩前后的对比。

技术分享

路径压缩在实际应用中效率很高,其一次查询复杂度平摊下来可以认为是一个常数。并且在实际应用中,我们基本都用带路径压缩的并查集。

 

带权并查集

所谓带权并查集,是指结点存有权值信息的并查集。并查集以森林的形式存在,而结点的权值,大多是记录该结点与祖先关系的信息。比如权值可以记录该结点到根结点的距离。

例题

在排队过程中,初始时,一人一列。一共有如下两种操作:

  • 合并:令其中的两个队列 A,BA,B 合并,也就是将队列 AA 排在队列 BB 的后面
  • 查询:询问某个人在其所在队列中排在第几位

例题解析

我们不妨设 size[] 为集合中的元素个数,dist[] 为元素到队首的距离,合并时,dist[A.root] 需要加上 size[B.root]

(每个元素到队首的距离应该是到根路径上所有点的 dist[] 求和)

size[B.root] 需要加上 size[A.root](每个元素所在集合的元素个数只需查询该集合中根的 size[x.root])

1) 初始化:

void init() {
    for(int i = 1; i <= n; i++)  {
        father[i] = i, dist[i] = 0, size[i] = 1;
    }
}

2) 查找:查找元素所在的集合,即根结点。

int get(int x) {
    if(father[x] == x) {
        return x;        
    }
    int y = father[x];
    father[x] = get(y);
    dist[x] += dist[y];  // x 到根结点的距离等于 x 到之前父亲结点距离加上之前父亲结点到根结点的距离
    return father[x];
}

路径压缩的时候,不需考虑 size[],但 dist[] 需要更新成到整个集合根的距离。

3) 合并

将两个元素所在的集合合并为一个集合。

通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。

void merge(int a, int b) {
    a = get(a);
    b = get(b);
    if(a != b) {  // 判断两个元素是否属于同一集合
        father[a] = b;
        dist[a] = size[b];
        size[b] += size[a];
    }
}

通过小小的改动,我们就可以查询并查集这一森林中,每个元素到祖先的相关信息。

看个题目

以上是关于并查集2——带权并查集的主要内容,如果未能解决你的问题,请参考以下文章

并查集2——带权并查集

CF553C Love Triangles(带权并查集)

带权并查集——食物链

总结一下我理解的带权并查集

并查集——poj2236(带权并查集)

带权并查集(个人模版)