【基础算法练习】并查集模板

文章目录

  • 算法思想
  • 代码模板
    • 题目描述:
    • 代码
    • 并查集模板
    • 模板题二(求并查集内集合的数量)

算法思想

并查集的核心操作:

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

基本原理:每个集合我们将他维护成一颗树,根节点的值就作为集合的编号,每个节点存储他的父节点,p[x] 就是 x 的父节点

  1. 当 p[x] == x 就证明 p[x] 是树根,就证明 x 指向的是根节点
  2. 我们可以用 while (p[x] != x) x = p[x] 来找到 x 的集合编号
  3. 我们可以用集合 A 的根节点连接上集合 B 的根节点的方式合并两个集合

接下来,看模板

代码模板

模板题:AcWing 836. 合并集合

题目描述:

【基础算法练习】并查集模板_第1张图片

代码

#include 
#include 

using namespace std;

const int N = 100010;

vector<int> p(N);

// 求 x 的祖先节点(集合的编号)
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]); // 如果 p[x] 不是 x 的祖先节点, 求 p[x] 的祖先并赋值给 p[x]
    return p[x]; // 返回 p[x]
}

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) p[i] = i;
    while (m -- ) {
        char ch;
        int a, b;
        cin >> ch >> a >> b;
        if (ch == 'M') p[find(b)] = find(a);
        else {
            if (find(a) == find(b)) cout << "Yes" << endl;
            else cout << "No" << endl;
        }
    }
    return 0;
}

并查集模板

// 求 x 的祖先节点(集合的编号)+ 路径压缩 
int find(int x) {
	// 如果 p[x] 不是 x 的祖先节点, 求 p[x] 的祖先并赋值给 p[x]
    if (p[x] != x) p[x] = find(p[x]); 
    return p[x]; // 返回 p[x]
}

并查集的核心操作 find 求 x 集合的编号(也就是求 x 的祖先节点),这模板包含并查集的路径压缩优化

如何理解?

传进来的参数 x 是集合内的一个数,我们调用 find(x) 就是为了求这个数的集合编号是什么(或者说,求 x 的祖先节点的编号)

记住前面并查集的性质,p[x] 是 x 的父节点,而 p[x] != x,而并查集的根节点的值就是并查集的编号,所以当 p[x] == x 的时候就证明 p[x] 是 x 的祖宗节点,所以这里 if (p[x] != x) p[x] = find(p[x]) 的操作

实际上就是:当 p[x] 不是 x 的祖宗节点,就让 p[x] = find(p[x]) 找他的祖宗节点,找到之后返回 p[x] 就是我们要求的 x 的祖宗节点,也就是集合的编号了

纯享版

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]); 
    return p[x];
}

模板题二(求并查集内集合的数量)

题目链接:AcWing 837. 连通块中点的数量
【基础算法练习】并查集模板_第2张图片

#include 
#include 
#include 

using namespace std;

const int N = 100010;

vector<int> p(N), Size(N);

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        p[i] = i;
        Size[i] = 1;
    }
    
    while (m -- ) {
        string s;
        int a, b;
        cin >> s;
        if (s == "C") {
            cin >> a >> b;
            if (find(a) == find(b)) continue; // 如果 a b 在同一个集合里, 那就没必要操作了
            Size[find(b)] += Size[find(a)]; // 将集合 a 元素的数量更新到集合 b 中
            p[find(a)] = find(b); // 合并集合 a, b(集合 a 并入集合 b)
        }
        else if (s == "Q1") {
            cin >> a >> b;
            if (find(a) == find(b)) cout << "Yes" << endl;
            else cout << "No" << endl;
        }
        else {
            cin >> a;
            cout << Size[find(a)] << endl;
        }
    }
    return 0;
}

我们可以维护一个 Size 数组,根节点上存好集合元素的数量,然后在合并集合的时候维护 Size 数组即可

你可能感兴趣的:(基础算法练习,算法)