并查集

发布时间 2023-04-07 21:18:10作者: 2huk

并查集

定义

并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。

存储

采用树的形式维护所有集合,树根的编号就是整个集合的编号。

对于每一个节点 \(i\),我们存储 \(p_i\) 为它的父亲节点。

最开始,对于所有 \(i \in [1, n]\),有 \(p_i = i\),也就是最开始所有数各属于一个集合。因此就有如果 \(i\) 点是一棵树的祖宗节点,那么 \(p_i = i\)

\(find(i)\) 函数

那如果想要知道 \(i\) 节点的祖宗节点,那么可以先找到它的父亲节点,再找到它父亲节点的父亲节点……最后如果 \(p_i = i\),则代表找到了根节点。

int find(int x){
    if(p[x] == x){      // 如果找到了祖宗节点,直接返回
        return x;
    }
    return find(p[x]);  // 否则查找它的父亲节点
}

时间复杂度大约 \(\Theta(1)\)

路径压缩优化

如果我们改变 \(p_i\) 的定义,使得 \(p_i\)\(i\) 号点的祖宗节点,那么以后查询时就不需要一遍一遍地查找祖宗节点了。

因此,在查找祖宗节点的过程中,如果遇到一个父亲节点,那么可以直接把这个父亲节点指向祖宗节点,即

int find(int x){
    if (p[x] == x){      // 如果找到了祖宗节点,直接返回
        return p[x];
    }
    p[x] = find(p[x]);  // 否则查找它的父亲节点,并将父亲节点也直接指向祖宗节点
}

应用

  1. 询问两个元素是否在一个集合当中
  2. 将两个集合合并

询问两个元素是否在一个集合当中

如果要询问 \(i\)\(j\) 元素是否在一个集合当中,那么只需要判断 \(find(i) = find(j)\) 即可。

时间复杂度 \(\Theta(1)\)

bool same_set(int x, int y){
    return find(x) == find(y);
}

将两个集合合并

如果要将两个集合合并,那么只需要在两颗树中插一条边即可,表示将两棵树合并为一棵树。

但是要更改集合的标号。即如果要将 \(i\) 号点所在的集合和 \(j\) 号点所在的集合合并,那么需要使 \(f_{find(i)} = find(j)\),这个操作表示把 \(j\) 所在的树的根节点下插入一颗子树,这颗子树就是 \(i\) 节点所在的树。

void merge(int x, int y){
    p[find(x)] = find(y);
}

变式题目

亲戚

团伙