【算法】——并查集

news2025/7/29 11:01:23

作者:指针不指南吗
专栏:算法篇

🐾或许会很慢,但是不可以停下🐾

文章目录

  • 1.思想
  • 2.模板
  • 3.应用
    • 3.1 合并集合
    • 3.2 连通块中点的数量

1.思想

并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)
比如说,我们可以用并查集来判断 某个节点是否属于某棵树等。

  • 并查集

    1. 将两个集合合并

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

  • 基本原理

    每个集合用一棵树来表示。

    树根的编号就是整个集合的编号。

    每个节点存储它的父节点,p[x]表示x的父节点。

  • 相关问题

    1. 如何判断树根:if(p[x]==x)

    2. 如何求x的集合编号:while(p[x]!=x) x=p[x]

    3. 判断两个元素是否在一个集合里面,即两个元素的编号相同:find(p[x])==find(p[y])

    4. 如何合并两个集合:px是x的集合编号,py是y的集合编号。

      p[x]=y,即让一个集合 a 的根节点指向另一个集合 b 的根节点,把a直接插在b的根节点下面。

  • 优化

    • 路径压缩:让每个节点都直接指向根节点(祖先)

2.模板

并查集的类型模板这里给出三种,具体题目具体分析,看看需要维护什么,添加什么。

(1)朴素并查集:

int p[N]; //存储每个点的祖宗节点

// 返回x的祖宗节点
int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;  //初始化每个元素的父节点和祖宗节点就是它本身

// 合并a和 b所在的两个集合:
p[find(a)] = find(b);   //让a的祖宗节点指向b的祖宗节点,a树插在b根节点下面

(2)维护size的并查集:

int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量

// 返回x的祖宗节点
int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
    p[i] = i;
    size[i] = 1;   //初始化,每个集合里面只有一个元素
}

// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];  //集合大小相加
p[find(a)] = find(b);

(3)维护到祖宗节点距离的并查集:

int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

// 返回x的祖宗节点
int find(int x)
{
    if (p[x] != x)
    {
        int u = find(p[x]);
        d[x] += d[p[x]];
        p[x] = u;
    }
    return p[x];
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
    p[i] = i;
    d[i] = 0;
}

// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量

3.应用

3.1 合并集合

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

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

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

输入格式

第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 M a bQ 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<bits/stdc++.h>
    using namespace std;
    
    const int N=100010;
    
    int n,m; //n表示点的数量,m表示操作的次数
    int p[N];  //存的每个节点的父节点
    
    int find(int x) //返回x的祖宗节点+路径压缩
    {
        if(p[x]!=x)  p[x]=find(p[x]);
        return p[x];
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        
        for(int i=1;i<=n;i++) p[i]=i; //最开始,每个点都各自在一个集合中,so父节点就是他本身;
        
        while(m--){
         
         char op[2];
         int a,b;
         scanf("%s%d %d",op,&a,&b);
         
         //合并
         if(op[0]=='M') p[find(a)]=p[find(b)];  //让a的祖宗节点等于b的祖宗节点,让a的祖宗节点直接插在b祖宗节点下面
         else{
             if(find(a)==find(b)) puts("Yes");  //判断是否属于同一个集合
             else puts("No");
         }  
        }
        
        return 0;
    }
    

注意

读入字母M或者是Q的时候,使用字符串op[2],是因为直接用char的话,可能会出现空格换行的问题作物,这种比较保险,记得在后面使用的时候,用op[0],不能直接使用op

puts自动包含换行

3.2 连通块中点的数量

给定一个包含 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 bQ2 a 中的一种。

输出格式

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

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

每个结果占一行。

数据范围

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

输入样例:

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

输出样例:

Yes
2
3
  • 思路

    • 连通块就是一个点的集合:集合中的点可以相互到达,直接或者是间接都是可以的;

    • 这时候我们可以把它类比成一个树,运用并查集,一个点集合,我们可以用一个编号来表示,属于同一个编号,就说明两个点之间可以相互到达,在一个连通块里面;

    • 有三个操作:

      1. 两点之间连一条边,那么这两个点所在集合中的点,都是可以相互到达的,即合成一个连通块,用并查集中的合并操作;

      2. 判断是否在一个连通块,用并查集的查询;

      3. 询问一个点集合的数量,需要我们额外维护,初始化的时候每个集合1个,合并的时候,两个集合数量相加,最后输出即可

  • 代码实现

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int N=1000010;
    int n,m;
    int p[N],sizel[N];  //p表示父节点,sizel表示集合的大小,记住sizel里面放的是祖宗节点,后面容易出错
    
    int find(int n)  //返回祖宗节点
    {
        if(p[n]!=n) p[n]=find(p[n]);
        return p[n];
    }
    
    
    int main()
    {
        scanf("%d%d",&n,&m);  //读入点的数量和操作的次数
        
        for(int i=1;i<=n;i++){   //初始化,父节点就是它本身;集合大小都是1,只有他自己
            p[i]=i;
            sizel[i]=1;
        }
        
        char op[5];   
        while(m--){
            scanf("%s",op);  //读入操作的名字
            
            if(op[0]=='C'){   //合并
            int a,b;
            scanf("%d%d",&a,&b);
            if(find(a)==find(b)) continue;  //相同则进入下个循环
            else{   //不同即操作,两步的顺序不能反!!!
                sizel[find(b)]+=sizel[find(a)];  //b的集合大小加上a的集合大小
                p[find(a)]=find(b);   //让a的祖宗节点指向b的祖宗节点
            }
         }
         
            else if(op[1]=='1'){  //查询是否一个集合
                int a,b;
                scanf("%d%d",&a,&b);
                if(find(a)==find(b))  puts("Yes");
                else puts("No");
            }
            
            else{
                if(op[1]=='2') {   //输出集合大小
                    int d;
                    scanf("%d",&d);
                    printf("%d\n",sizel[find(d)]);  
                }
            }
        }
        
        return 0;
    }
    

Alt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/367895.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESP32 Arduino EspNow点对点双向通讯

ESP32 Arduino EspNow点对点双向通讯✨本案例分别采用esp32和esp32C3之间点对点单播无线通讯方式。 &#x1f33f;esp32开发板 &#x1f33e;esp32c3开发板 &#x1f527;所需库(需要自行导入到Arduino IDE library文件夹中&#xff0c;无法在IDE 管理库界面搜索下载到该库)&am…

【GO】k8s 管理系统项目[前端部分16–前端布局]

【GO】k8s 管理系统项目[前端部分–前端布局] 1. 前端布局 2. Layout 2.1 layout src/layout/Layout.vue <template><div class"common-layout"><el-container><el-side width"200">Aside</el-side><el-container>…

哪些骨传导运动蓝牙耳机好,分享几款不错的骨传导耳机

​骨传导耳机在运动中有很多优势&#xff0c;它是一款不入耳的耳机&#xff0c;适合在跑步、骑行、爬山等运动中使用&#xff0c;如果你是一个爱运动的人&#xff0c;骨传导耳机是不错的选择。由于骨传导技术不需要塞入耳朵中就能听到音乐&#xff0c;所以不会产生任何不适感。…

计算机图形学期末复习笔记

计算机图形学 ch1绪论 1.1计算机图形学及其概念 计算机图形学&#xff08;Computer Graphics&#xff09;是研究怎样利用计算机来生成、处理和显示图形的原理、方法和技术的学科。 cg研究对象是图形 图形的要素 几何&#xff08;轮廓、点、线、面&#xff09;非几何要素&…

前向传播与反向传播参数的更新方式(略高于高中数学水平)(附公式、代码)

前向传播与反向传播意义及其参数的更新方式 文章目录前向传播与反向传播意义及其参数的更新方式一、前言二、前反向传播的作用三、前向传播四、反向传播代码一、前言 因为本身非科班出身&#xff0c;数学又学的很差&#xff0c;一直都是傻瓜式地用tensorflow和pytorch搭网络。…

【容器】学习docker容器网络

在前面讲解容器基础时&#xff0c;曾经提到过一个 Linux 容器能看见的“网络栈”&#xff0c;实际上是被隔离在它自己的 Network Namespace 当中的。 而所谓“网络栈”&#xff0c;就包括了&#xff1a;网卡&#xff08;Network Interface&#xff09;、回环设备&#xff08;L…

Mac mini 外接移动硬盘无法写入或者无法显示的解决方法

文章目录1. 背景2. 让NTFS格式的移动硬盘正常读写方法3. 打开“启动安全性实用工具”4. 更改“安全启动”设置1. 背景 刚买mac min&#xff08;2023年2月3日&#xff09;不久&#xff0c;发现macOS的玩起来并不容易&#xff0c;勇习惯了windows系统的习惯&#xff0c;感觉 mac…

【storybook】你需要一款能在独立环境下开发组件并生成可视化控件文档的框架吗?(二)

storybook回顾继续说说用法配置文件介绍回顾 上篇博客地址&#xff1a; https://blog.csdn.net/tuzi007a/article/details/129192502说了部分用法。 继续说说用法 配置文件介绍 开发环境的配置都在.storybook目录中&#xff0c;里面包含了2个文件 main.js preview.js先看m…

STM32 触摸屏移植GUI控制控件

目录 1、emWin 支持指针输入设备。 2、 模拟触摸屏驱动 3、实现触摸屏的流程 3.1 实现硬件函数 3.2 实现对GUI_TOUCH_Exec()的定期调用 3.3 使用上一步确定的值&#xff0c;在初始化函数LCD_X_Config&#xff08;&#xff09;当中添加对GUI_TOUCH_Calibrate()的调用 4、…

Kubernetes入门教程 --- 使用二进制安装

Kubernetes入门教程 --- 使用二进制安装1. Introduction1.1 架构图1.2 关键字介绍1.3 简述2. 使用Kubeadm Install2.1 申请三个虚拟环境2.2 准备安装环境2.3 配置yum源2.4 安装Docker2.4.1 配置docker加速器并修改成k8s驱动2.5 时间同步2.6 安装组件3. 基础知识3.1 Pod3.2 控制…

【一些回忆】2022.02.26-2023.02.26 一个普通男孩蜕变的365天

&#x1f483;&#x1f3fc; 本人简介&#xff1a;男 &#x1f476;&#x1f3fc; 年龄&#xff1a;18 &#x1f91e; 作者&#xff1a;那就叫我亮亮叭 &#x1f4d5; 专栏&#xff1a;一些回忆 为什么选择在这个时间节点回忆一下呢&#xff1f; 一是因为今天距离2023高考仅剩1…

双指针法应用总结

一、双指针法&#xff08;一&#xff09;概况1.类型&#xff1a;快慢指针&#xff08;相同方向循环&#xff09;、对撞指针&#xff08;相反方向循环&#xff09;、滑动窗口2.用途&#xff1a;提高效率&#xff0c;通常能将将O(n^2)的时间复杂度&#xff0c;降为O(n)3.可应用的…

selenium基本操作

爬虫与反爬虫之间的斗争爬虫&#xff1a;对某个网站数据或图片感兴趣&#xff0c;开始抓取网站信息&#xff1b;网站&#xff1a;请求次数频繁&#xff0c;并且访问ip固定&#xff0c;user_agent也是python&#xff0c;开始限制访问&#xff1b;爬虫&#xff1a;通过设置user_a…

数据库浅谈之 DuckDB AGG 底层实现

数据库浅谈之 DuckDB AGG 底层实现 HELLO&#xff0c;各位博友好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 这里是数据库浅谈系列&#xff0c;收录在专栏 DATABASE 中 &#x1f61c;&#x1f61c;&#x1f61c; 本系列阿呆将记录一些数据库领域相关的知…

离线维基百科阅读器Kiwix Serve

本文软件是网友 刘源 推荐的&#xff0c;因为他已经安装成功了&#xff0c;所以老苏拖拖拉拉的就从去年拖到了现在&#xff1b; &#x1f602; 什么是 Kiwix ? Kiwix 是一个用于浏览离线内容的自由开源浏览器&#xff0c;最初用于离线浏览维基百科。Kiwix 可以读取以压缩形式存…

[神经网络]基干网络之VGG、ShuffleNet

一、VGG VGG是传统神经网络堆叠能达到的极限深度。 VGG分为VGG16和VGG19&#xff0c;其均有以下特点&#xff1a; ①按2x2的Pooling层&#xff0c;网络可以分成若干段 ②每段之内由若干same卷积操作构成&#xff0c;段内Feature Map数量固定不变&#xff1b; ③Feature Map按2的…

对个人博客系统进行web自动化测试(包含测试代码和测试的详细过程)

目录 一、总述 二、登录页面测试 一些准备工作 验证页面显示是否正确 验证正常登录的情况 该过程中出现的问题 验证登录失败的情况 关于登录界面的总代码 测试视频 三、注册界面的自动化测试 测试代码 过程中出现的bug 测试视频 四、博客列表页测试&#xff08;…

【Leedcode】数据结构中链表必备的面试题(第四期)

【Leedcode】数据结构中链表必备的面试题&#xff08;第四期&#xff09; 文章目录【Leedcode】数据结构中链表必备的面试题&#xff08;第四期&#xff09;1.题目2.思路图解(1)思路一(2)思路二3.源代码总结1.题目 相交链表&#xff1a; 如下&#xff08;示例&#xff09;&…

小白福利!我开发了一个快速部署库

1、开发背景 很多入门的同学&#xff0c;在跟着视频敲完代码之后&#xff0c;在打包出来的产物犯了难 如果是 hash 路由&#xff0c;要么使用后端部署&#xff0c;要么使用 github 或者 gitee 提供的静态部署服务如果是 history 路由&#xff0c;那只能使用后端框架进行部署&a…

内网渗透(五十三)之域控安全和跨域攻击-利用域信任密钥获取目标域控

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…