算法基础系列第二章——出发,浅酌并查集

例题:集合合并

原题描述

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

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

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

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

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

每个结果占一行。

数据范围
1≤n,m≤105
输入样例:
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:
Yes
No
Yes

参考代码(C++版本)

#include <iostream>

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]);//递归去让x直接找到它的父节点,并直接指向父节点
    return p[x];
}

//合并两个子集合的函数
void merge(int a,int b)
{
    int t1,t2;
    t1 = find(a);
    t2 = find(b);
    
    //判断两个是否在一个集合,也就是祖先是否一样
    if(t1 != t2) p[t1]  = t2;
}

int main()
{
    cin >>n >>m;
    
    for(int i = 1;i <= n;i++) p[i] = i;//让自己做自己的祖先,把自己孤立起来

    //m次查询
    while(m--)
    {
        char op[2];
        int a,b;
        
        scanf("%s %d %d",op,&a,&b);
        
        // if(op[0] == 'M') p[find(a)] = find(b);
        if(*op == 'M') merge(a,b);
        else
        {
            if(find(a) == find(b)) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

知识储备 ⭐⭐⭐

什么是并查集

并查集是一种用来管理元素分组情况的数据结构。并查集也称不相交集数据结构
并查集贯彻的思想是擒贼先擒王,只关注“老大”是谁、“祖宗”是谁。
boss

并查集的作用

并查集可以动态地联通两个点,并且可以非常快速的判断两个点是否联通。并查集可以高效地进行查询元素a和元素b是否属于同一组合并元素a和元素b所在的组操作。不过需要注意并查集虽然可以进行合并操作,但是却无法进行分割操作

并查集的使用

假设存在n个结点,我们先将所有结点的父结点标记为自己;每次要链接结点i和j的时候,我们可将i的父亲标记为j;每次查询两个结点是否相连时,就可以通过查找i和j的祖先最终是否是一个人。

并查集的使用前提

我们先将所有结点的父结点标记为自己。让每一个点最初保持孤立的状态,然后就通过一些后序给的条件,将看起来关系散乱的各个点逐渐合并起来。

并查集的核心🍭🍭🍭

1. 查询元素a和元素b是否属于同一组 --> find()函数:返回祖宗结点的同时压缩路径

这是一个“找父亲”的递归函数,不停的去找父亲。找父亲,找父亲的父亲,找父亲的父亲的父亲… 直到最后找到源头的祖宗为止。

最后返回p[x]实现压缩路径,让x的父结点直接变成祖宗结点。保证让自己可以直接和祖宗产生联系,那么再次查找这个x的时候,就不用逐层逐层的找了。

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

皮一下

2. 合并元素a和元素b所在的组 --> merge()函数

```
    void merge(int a,int b)
    {
        int t1,t2;
        //找到a ,b 各自的祖先
        t1 = find(a);
        t2 = find(b);
        
        if(t1 != t2) //判断两个是否在一个集合,也就是祖先是否一样
        {
            //让a的祖先结点中存放b的祖先的信息。现在a也是b的祖先旗下的一枚子民了
            p[t1]  = t2;
        }
    } 
```

剖析例题⭐⭐⭐

题干要求

题目要让我们实现的

1、将编号为 a 和 b 的两个数所在的集合合并

2、询问编号为 a 和 b 的两个数是否在同一个集合中

分析

对于查询而言,主要是依靠find函数实现,由于上文已经详解,此处不在赘述

对于合并而言,主要是依靠merge函数实现,由于上文已经详解,此处不在赘述

需要注意的点

我们先将所有结点的父结点标记为自己

for(int i = 1;i <= n;i++) p[i] = i;

持续更新基础算法中ing

写在最后,谢谢观看,若有偏颇,请及时指正( ^ - ^ )

坚持

  • 25
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨枝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值