并查集

上一篇博客:Trie树简介及其应用

 写在前面:大家好!我是AC-fun,我的昵称来自两个单词Acceptedfun。我是一个热爱ACM的蒟蒻。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教。我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油,冲鸭!
用知识改变命运,用知识成就未来!加油 (ง •̀o•́)ง (ง •̀o•́)ง

文章目录

  • 概念
  • 基本原理
  • 应用
  • 初始化及如何判断树根
  • 如何求x的集合编号
  • 如何将两个集合合并
  • 例题
    • 题目信息
      • 题目描述
      • 输出格式
      • 数据范围
      • 输入样例:
      • 输出样例:
    • 题解
      • 解题代码

概念

 并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。并查集的思想是用一个 数组 表示了整片森林(parent),树的根节点唯一标识了一个集合,我们只要找到了某个元素的的树根,就能确定它在哪个集合里。

基本原理

 每一个集合用一棵树来表示。树根的编号就是整个集合的编号。每个节点存储它的父节点, p[x] 表示 x 的父节点。

应用

 主要用于解决一些元素分组的问题。它管理一系列不相交的集合,并支持两种操作:

  • 合并(Union):把两个不相交的集合合并为一个集合。
  • 查询(Find):查询两个元素是否在同一个集合中。

初始化及如何判断树根

 并查集的重要思想在于,用集合中的一个元素代表集合
并查集_第1张图片
 因为刚开始每个集合只有一个点,所以直接让本身作为父节点即可。p[x] = x 是祖宗节点的一个标志,因为只要不是祖宗节点,那么p[x] 存储的是 x 的父节点。如果 p[x] = x,说明 x 已经没有父节点了,即当前的 x 为祖宗节点。
并查集_第2张图片
 观察上图可以发现 3 成为了 1 的子节点,即 p[3] = 1, 3 的父节点是 1。而p[1] = 1,所以 1 没有父节点了,所以 1 就是一个祖宗节点。

如何求x的集合编号

 一层一层访问父节点,直至祖宗节点(祖宗节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的祖宗节点是否相同即可。只要 p[x] != x,说明 x 还不是祖宗节点。直到 p[x] = x 为止,此时的 x 即为要查找元素的集合编号。用代码表示为:

while(p[x] != x) x = p[x];

 但是每次都这样查找的话时间复杂度太高,因此这里有一个优化的方法:路径压缩。使用递归在求 x 的集合编号的同时进行路径压缩,只要当前的点不是祖宗节点,那么就让当前的点指向祖宗节点。

// 返回 x 的祖宗节点 + 路径压缩(递归实现)
int find(int x) {
     
    // 只要当前的点不是祖宗节点,那么就让当前的点指向祖宗节点
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

如何将两个集合合并

 如何将两个元素合并呢?例如下图,如何将节点为 53 的树合并呢?
并查集_第3张图片
 只需要找到 53 所在树的祖宗节点,然后直接将 5 或者 3 的祖宗节点作为另一个树的的父节点即可。伪代码为:

p[find(3)] = find(5);

合并后的结果:并查集_第4张图片


例题

题目来源:AcWing 836. 合并集合

题目信息

题目描述

一共有 n 个数,编号是 1 ~ n,最开始每个数各自在一个集合中。

现在要进行 m 个操作,操作共有两种:

  1. “M a b”,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. “Q a b”,询问编号为 a 和 b 的两个数是否在同一个集合中;
    输入格式
    第一行输入整数n和m。

接下来 m 行,每行包含一个操作指令,指令为 “M a b” 或 “Q a b” 中的一种。

输出格式

对于每个询问指令”Q a b”,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 “Yes”,否则输出 “No”

每个结果占一行。

数据范围

1 ≤ n, m ≤ 1 0 5 10^5 105

输入样例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

输出样例:

Yes
No
Yes

题解

解题代码

#include
#include
using namespace std;

const int N = 100010;
int n, m;
int p[N];

// 返回 x 的祖宗节点 + 路径压缩(递归实现)
int find(int x) {
     
    // 只要当前的点不是祖宗节点,那么就让当前的点指向祖宗节点
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {
     
    cin >> n >> m;
    for (int i = 1; i <= n; i++) p[i] = i;  
    while (m--) {
     
        char op[2];  // 最好使用数组的方式,因为如果使用 %c 接收很容易出现接收空格等的情况
        int a, b;
        scanf("%s%d%d", op, &a, &b);
        if (op[0] == 'M') p[find(a)] = find(b);
        else {
     
            if (find(a) == find(b)) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

图片来源:算法学习笔记(1) : 并查集


未完待续,持续更新中……
并查集_第5张图片

你可能感兴趣的:(AcWing,数据结构,并查集,算法,C++)