算法--并查集

目录

1.并查集 

1.1并查集的作用

1.2基本思想

1.3示例说明

1.4优化:路径压缩

2.例题:合并集合

2.1题目描述

2.2代码展示

3.例题:连通块中点的数量

3.1题目描述

3.2代码展示


1.并查集 

1.1并查集的作用

  • 并(Union):将两个不相交的集合合并成一个集合。
  • 查(Find):确定某个元素属于哪个集合,通常返回该集合的代表元素。

1.2基本思想

并查集通常使用数组p来实现,数组中的每个元素代表一个节点,数组的值表示该节点的父节点。初始时,每个节点的父节点是它自身,表示每个节点单独构成一个集合。

1.3示例说明

🌰假设我们有一个包含 6 个节点的并查集,节点编号从 0 到 5。

  • 初始化

    在初始化阶段,每个节点的父节点都是它自身。我们使用一个数组 parent 来存储每个节点的父节点信息,此时 parent 数组的初始状态为:

parent[0] = 0;
parent[1] = 1;
parent[2] = 2;
parent[3] = 3;
parent[4] = 4;
parent[5] = 5;

可以将其理解为每个节点都独立构成一个集合,此时有 6 个集合,每个集合只有一个元素。

  • 合并操作

    🌰假设我们要合并节点 0 和 1 所在的集合。

    • 首先,查找节点 0 的根节点,通过 parent[0] 发现它的父节点是 0,所以 0 就是它所在集合的根节点。
    • 接着,查找节点 1 的根节点,parent[1] = 1,所以 1 是它所在集合的根节点。
    • 由于 0 和 1 是不同的根节点,我们将其中一个根节点(比如 0)的父节点设置为另一个根节点(1),即 parent[0] = 1。此时 parent 数组变为:
parent[0] = 1;
parent[1] = 1;
parent[2] = 2;
parent[3] = 3;
parent[4] = 4;
parent[5] = 5;

现在节点 0 和 1 属于同一个集合,这个集合的根节点是 1。

        🌰再假设我们要合并节点 2 和 3 所在的集合:

  • 查找节点 2 的根节点,parent[2] = 2,根节点是 2。
  • 查找节点 3 的根节点,parent[3] = 3,根节点是 3。
  • 将 2 的父节点设置为 3,即 parent[2] = 3parent 数组变为:
parent[0] = 1;
parent[1] = 1;
parent[2] = 3;
parent[3] = 3;
parent[4] = 4;
parent[5] = 5;

此时节点 2 和 3 属于同一个集合,根节点是 3。

  • 查找操作

            比如我们要查找节点 0 所在集合的根节点。

    • 首先访问 parent[0] = 1,发现 1 不是根节点(因为 parent[1] != 1 不成立)。
    • 接着访问 parent[1] = 1,此时 1 是根节点,所以节点 0 所在集合的根节点是 1。
  • 判断连通性

    判断节点 0 和 1 是否连通,通过查找它们的根节点,发现都是 1,所以节点 0 和 1 是连通的。
    判断节点 0 和 2 是否连通,查找节点 0 的根节点是 1,查找节点 2 的根节点是 3,根节点不同,所以节点 0 和 2 不连通。

1.4优化:路径压缩

📍路径压缩的核心思想:

在查找元素的过程中,将该元素到根节点路径上的所有节点直接连接到根节点,从而减少后续查找操作的时间复杂度。

具体来说,当我们查找一个元素的根节点时,在递归返回的过程中,将该元素及其路径上的所有祖先节点的父节点都直接设置为根节点。

2.例题:合并集合

2.1题目描述

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

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

  1. M a b,将编号为 aa 和 bb 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. Q a b,询问编号为 aa 和 bb 的两个数是否在同一个集合中;

输入格式

第一行输入整数 n 和 m。

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

输出格式

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

每个结果占一行。

数据范围

1≤n,m≤ \(10^{5}\)  

输入样例:

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

输出样例:

Yes
No
Yes

2.2代码展示

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

const int N = 1e5 + 10;
int parent[N];
int n, m;

// 📍查找祖宗结点(优化:路径压缩)📍
int find(int x) {

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

//合并
void merge(int x,int y) {

    int px = find(x);
    int py = find(y);
    if (px != py) {
        parent[px] = py;
    }
}

//查找
void check(int x, int y) {

    int px = find(x);
    int py = find(y);
    if (px == py) cout << "Yes" << endl;
    else cout << "No" << endl;
}

void cal() {

    //初始化操作:每个结点的父节点都为自身,表示每个节点单独构成一个集合
   for (int i = 1; i <= n; i++) {
        parent[i] = i;
    }
   for (int i = 0; i < m; i++) {

       char ch;
       int x, y;
       cin >> ch >> x >> y;
       if (ch == 'M') merge(x, y);
       else check(x, y);
   }  
}

signed main() {
    cin >> n >> m;
    cal();
    return 0;
}

3.例题:连通块中点的数量

3.1题目描述

给定一个包含 n个点(编号为 1∼n)的无向图,初始时图中没有边。

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

  1. C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
  2. Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
  3. Q2 a,询问点 a 所在连通块中点的数量;

输入格式

第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 C a bQ1 a b 或 Q2 a 中的一种。

输出格式

对于每个询问指令 Q1 a b,如果 a 和 b 在同一个连通块中,则输出 Yes,否则输出 No

对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量

每个结果占一行。

数据范围

1≤n,m≤ \(10^{5}\)  

输入样例:

5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

输出样例:

Yes
2
3

3.2代码展示

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

const int N = 1e5 + 10;
int parent[N], Size[N]; // 修改为小写的 size
int n, m;

// 查找元素 x 所在集合的代表元,并进行路径压缩⚠️
int find(int x) {
    if (x != parent[x]) {
        parent[x] = find(parent[x]);
    }
    return parent[x];
}

// 合并元素 x 和 y 所在的集合
void merge(int x, int y) {
    int px = find(x);
    int py = find(y);
    if (px != py) {
        parent[px] = py;
        Size[py] += Size[px];  //📍📍📍
    }
}

// 检查元素 x 和 y 是否在同一集合
void check(int x, int y) {
    int px = find(x);
    int py = find(y);
    if (px == py) {
        cout << "Yes" << endl;
    } else {
        cout << "No" << endl;
    }
}

// 处理输入并执行相应操作
void cal() {
    // 初始化每个元素的父节点为自身,集合大小为 1
    for (int i = 1; i <= n; i++) {
        parent[i] = i;
        Size[i] = 1;    //📍📍📍
    }
    // 处理 m 次操作
    for (int i = 0; i < m; i++) {
        string str;
        int x, y;
        cin >> str;
        if (str == "C") {
            cin >> x >> y;
            merge(x, y);
        } else if (str == "Q1") {
            cin >> x >> y;
            check(x, y);
        } else if (str == "Q2") {
            int node;
            cin >> node;
            // 输出 node 所在集合的代表元对应的集合大小
            cout << Size[find(node)] << endl;  //Size[node]不正确的!!⚠️
        }
    }
}

signed main() {
    cin >> n >> m;
    cal();
    return 0;
}    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值