并查集 一种快速处理集合查找和合并的数据结构

并查集的用途

顾名思义,并查集主要的作用就是用来处理集合的合并与集合元素的查找(判定元素是否在该集合中),其合并和查找操作时间复杂度可以近似看作O(1)。并查集其实是一种“使用树存储集合”的思想,树的root节点是整个集合的标志,区别不同集合的方式就是判断树的root节点是否相同。

左边的树的root节点为1,则该集合可认为是集合1,右侧为集合4。在使用树作为存储形式时,如果想知道3所属的集合是哪个,只需要寻找一层层寻找3的祖宗节点,直至root节点,则可以判断该节点所属集合。(对于root节点来说,它的父节点是它本身,所以只需要递归寻找到父节点和本身相同的节点即可停止递归)

在合并集合时,只需要将root节点变更为另一集合的子节点。此时集合1中的所有元素的root节点为4,则他们都属于集合4。

路经压缩

在上述算法中,查找元素的时间复杂度需要取决于树的高度O(h),我们在确定某个元素属于集合时,可以将路径上所有的祖宗节点的父节点更换为root节点。比如我们查找到节点5属于集合1,那么就将查找路径上所有节点的指向更改。

该优化称为路经压缩,经过优化后,再次查找节点5所属集合,只需一次查找,时间复杂度为O(1)。

代码实现

例题参考:https://www.acwing.com/problem/content/838/

#include<iostream>

using namespace std;

const int N = 100000 + 10;
int p[N]; // p[i] 为 i 的父节点

int find(int x) {
    // 寻找x所属集合,同时进行路径压缩
    if (x == p[x]) return x;
    p[x] = find(p[x]);
    return p[x];
}

int main() {
    // 初始时,每个数各自作为一个集合,父节点是自己
    for (int i = 0; i < N; i ++) p[i] = i;

    int n, m;
    scanf("%d%d", &n, &m);

    while (m -- ) {
        char op[2];
        int a, b;
        scanf("%s%d%d", op, &a, &b);

        if (op[0] == 'M') {
            // 合并集合时,只需要将集合1的root节点的父节点指向为另一个集合的root节点
            p[find(a)] = find(b);
        } else {
            if (find(a) == find(b)) printf("Yes\n");
            else printf("No\n");
        }
    }
}

并查集的增强

在原有并查集的基础上,我们可以通过增加空间来记录更多的信息。例如我们可以维护每个集合的元素个数,这只需要在集合合并时,个数相加。我们还可以维护每个节点到root节点的距离,这种方式也叫做带权重的并查集。

posted @ 2022-01-05 18:38  moon_orange  阅读(89)  评论(0编辑  收藏  举报