并查集

$$\color{#80C0FE}\mathsf{『我们天空\ 何时才能成一片』}$$$$\color{#80C0FE}\mathsf{『我们天空\ 何时能相连』}$$$$\color{#80C0FE}\mathsf{『等待在世界的各一边』}$$$$\color{#80C0FE}\mathsf{『任寂寞\ 嬉笑一年一年』}$$$$\color{#80C0FE}\mathsf{『天空叠着\ 层层的思念』}$$$$\tag*{\color{black}\text{——《天空》 王菲 1994}}$$$$\color{#FFFFFF}\mathsf{『天空叠着 层层的思念』}$$

并查集

背景介绍

并查集(union-find disjoint sets),在一些有 $n$ 个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中。其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1秒)内计算出试题需要的结果,只能用并查集来描述。

并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。

思路

建立一个森林,初始时森林中有 $n$ 棵高度为 $1$ 的并查集树,此时所有点相互独立成树,每棵树都是有根树,初始时跟就是他本身。

当两个点合并时,只要将两点所在的树合并即可,即将其中一个点所在的树挂在另一个点所在的树下,方法是将一棵树的祖先(根)节点挂在另一棵树的祖先(根)节点下,建立一条连接两树祖先节点的边即可。

判断两个点是否属于同一个集合时,只需判断两个点是否属于同一棵树,判断的方法是判断所在的有根树的祖先(根)是否相同即可。

由此可以看出,并查集的操作有两种:“并”和“查”,“并”即合并(union),“查”即查找(find),所以并查集的英文是“union-find disjoint sets”,而在这里要注意的是,union 是 C 语言关键字,在书写时不能使用作为函数名,所以我们通常使用 merge 取而代之。

优化方式

很容易发现,如果并查集直接暴力实现,它的时间复杂度是很高的,在这个情况下,我们有两种方式优化并查集。

并查集的优化有两种,分别是 路径压缩按秩合并,其中,路径压缩针对“查”的操作,而按秩合并针对“并”的操作。

路径压缩

路径压缩优化时写并查集时最常使用的操作。

设想,我们可以将树的高度压缩的越小,那么我们的查询操作就会越快,因为我们这样就很快能搞找到根节点,那么当我们进行查询操作时,如果对于任何一个节点进行搜寻祖宗节点时,我们将沿路的所有节点都直接挂在根节点下,这样便可以更快速的查询了。

按秩合并

这个优化不是并查集中比较常见的优化。

在合并时,如果我们将一棵小树挂在了大树下,那么很容易知道,我们后续的查询操作会更简单,如果将一棵大树挂在小树下,运行速度会更慢,那么我们如果能记录每棵树的节点数量,在合并的时候对其大小进行判断,那么就能更好的进一步优化并查集了。

但是很容易发现,在有路径压缩的前提下,按秩合并优化对时间的影响并不大,甚至可以说是微乎其微,而且还会浪费一定的空间($2$ 倍空间),所以很多选手在写代码时不会写按秩合并优化。


优化后,并查集的效率变高,并查集每次合并和查询的时间复杂度为 $\operatorname{Ackermann}^{-1}(n)$,即阿克曼函数的反函数的时间复杂度,在 $n$ 在 int 范围内时,$\operatorname{Ackermann}^{-1}(n)<5$,所以可以忽略为常数。

注意:大部分时候,为了节省时间(懒省事),只写路径压缩优化,不写按秩合并优化。

代码

#include <bits/stdc++.h>

using namespace std;

const int N=2e5+2;

struct dsu {
    int fa[N];
    void init(int n) {
        for(int i=1;i<=n;++i) fa[i]=i;
   }
    int find(int id) {
        return (fa[id]==id)?id:fa[id]=find(fa[id]);
    }
    void merge(int a,int b) {
        fa[find(a)]=find(b);
    }
};

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    dsu s;
    int n,m;
    cin>>n>>m;
    s.init(n);

    while(m--) {
        int op,a,b;
        cin>>op>>a>>b;

        if(op==1) s.merge(a,b);
        else {
            if(s.find(a)==s.find(b)) cout<<"Y\n";
            else cout<<"N\n";
        }
    }

    return 0;
}

练习题:https://www.luogu.com.cn/problem/P3367

练习题单:https://www.luogu.com.cn/training/3065#problems

 
posted @ 2023-04-09 16:15  abensyl  阅读(18)  评论(0)    收藏  举报  来源
//雪花飘落效果