并查集的查询与合并详解

news/2024/11/27 23:47:32/

文章目录

一、并查集的概念

二、并查集的实现

2、1 并查集不同集合(树)的形成

2、2 find()函数找一个元素集合的编号(元素所属于树的祖宗)

2、3  合并两个不同集合(合并两棵不同的树)

2、4 查询两个元素是否在一个集合

2、5 并查集例题训练1

2、6 并查集例题训练2

三、总结


标题:并查集的查询与合并详解

作者:@Ggggggtm

寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景

一、并查集的概念

  并查集是一种树形的数据结构。使用树型结构来存储数据。树根的编号即为整个树的标号,且每个节点存储的数据是他的父节点下标

  并查集被很多OIer认为是最简洁而优雅的数据结构之一,主要用于解决一些元素分组的问题。它管理一系列不相交的集合,并支持两种操作:

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

二、并查集的实现

2、1 并查集不同集合(树)的形成

   我们把并查集不同集合(树)的实现主要分为以下几个点:

  1. 我们先给出n个数据,把n个数据存储到不同的集合当中(p[ i ] = i),在这里我们把每个p[ i ]分别看成一个不同集合(也就是一棵树)。
  2. p[ i ] = i,i即为这棵树的编号,这颗树下面的孩子节点存储的数据是父节点的下标。
  3. 当p[ i]=i 时,就相当于找到了根节点。
  4. 我们刚开始每个集合中的元素只有一个。后续合并后,集合元素个数不断增加。

2、2 find()函数找一个元素集合的编号(元素所属于树的祖宗)

  我们查找一个元素的集合,把元素的当作下标传给find()函数,代码如下:

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

  我们p[x]中存储的正是他的父节点,从而就可以一直往上查找,直到p[ x ]=x时结束。当p[ x ]=x时,就相当于找到了根节点。此时的p[ x ]存储的是这棵树的编号。我这发现,刚开始每个集合当中都只有一个元素,也就是p[ x ],后面我们会对不同的集合进行合并,使得一个集合有多个元素。

  我们再找祖宗节点时进行了路径压缩。什么是路径压缩呢?路径压缩就是我们在查找某个元素的祖宗时,在找父节点的这条路经上的元素都指向祖宗节点,以便于我们后面的查找的时间复杂度近乎O(1)。

2、3  合并两个不同集合(合并两棵不同的树)

  我们直到了每棵树的根节点存储的是这个树的编号,而不是父节点。当我们要合并两颗树时,我们只需要把一棵树的根节点存储的编号改为另一棵树的根节点编号。简单的理解就是一个树的根节不再是根节点,而是一个子节点,该树的根节点存储的也不再是编号,而是存储的父节点,该父节点就是另一棵树的根节点。我们看代码:

//合并  把a的祖宗节点的父节点当作b的祖宗结点
p[find(a)]=find(b);

2、4 查询两个元素是否在一个集合

  我们有了find()函数,就可以很简单的判断出两个元素的是否在同一个集合当中(两个元素是否在同一个树中)。我们只需要判断两个元素集合的编号是否相同(两个元素的祖宗节点是否相同)即可。我们看代码:

//看a、b两个元素是否在同一个集合当中
if(find(a)==find(b))cout<<"Yes"<<endl;
elsecout<<"No"<<endl;

2、5 并查集例题训练1

 一共有 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≤10e5     1≤n,m≤10e5

输入样例:

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

输出样例:

Yes
No
Yes

  答案如下:

#include<iostream>
using namespace std;const int N=100010;
int p[N];//找祖宗+路径压缩
int find(int x)
{if(p[x]!=x){p[x]=find(p[x]);}return p[x];
}
int main()
{int n,m;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)p[i]=i;while(m--){char op[2];int a,b;cin>>op>>a>>b;if(op[0]=='M'){//合并  把a的祖宗节点的父节点当作b的祖宗结点p[find(a)]=find(b);}else{if(find(a)==find(b))cout<<"Yes"<<endl;elsecout<<"No"<<endl;}}return 0;
}

2、6 并查集例题训练2

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

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

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

输入格式

第一行输入整数 nn 和 mm。

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

输出格式

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

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

每个结果占一行。

数据范围

1≤n,m≤1051≤n,m≤105

输入样例:

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

输出样例:

Yes
2
3

  我们这个题相对于上个题就是对出了一个统计一个集合元素的个数。整体思路大同小异,我们直接看代码解析:

#include<iostream>
using namespace std;const int N=100010;
int p[N],cnt[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=0;i<n;i++){p[i]=i;cnt[i]=1;}while(m--){char op[5];int a,b;scanf("%s",op);if(op[0]=='C'){scanf("%d%d",&a,&b);if(find(a)==find(b))continue;cnt[find(b)]+=cnt[find(a)];p[find(a)]=find(b);}else if(op[1]=='1'){scanf("%d%d",&a,&b);if(find(a)==find(b))printf("Yes\n");elseprintf("No\n");}else{scanf("%d",&a);printf("%d\n",cnt[find(a)]);}}return 0;
}

   注意,我们刚开始每个集合中的元素只有一个。后续合并后,集合元素个数不断增加。

三、总结

  我们主要掌握find()函数,并查集算法中,最为核心的就是find()函数。在这个算法中,路径压缩给我们的算法效率提高了很多,这个也是需要理解的。 合并、查询是并查集的两个主要操作,我们也应该熟悉理解。

  我们对并查集的讲解就到这里,希望以上内容对你有所帮助。

  感谢阅读ovo~ 


http://www.ppmy.cn/news/10869.html

相关文章

Vue3兄弟,爷孙组件间传值【包含手撸发布/订阅类】

Vue2【Vue基础十】— 兄弟组件之间传值【中央事件总线&#xff0c;消息订阅与发布】 Vue2 : 【Vue基础九】–父子组件传值 一、 兄弟组件 1-1 事件总线 使用Vue3&#xff0c;会发现&#xff0c;原本得心应手的eventBus突然不灵了Vue3不再提供$on与emit函数&#xff0c;Vue实例…

Pytorch - 使用pytorch自带的Resnet作为网络的backbone

在使用Pytorch搭建自己的神经网络框架时&#xff0c;经常需要使用Pytorch中内置的torchvision.models中的模型作为特征提取的Backbone&#xff0c;然后再在这个基础上进行更加复杂的网络搭建。 在这里以使用Pytorch中内置的Resnet18为例&#xff0c;如何作为Backbone层进行使用…

环形缓冲区

文章目录一. 什么是环形缓冲区&#xff1f;二、实现环形缓冲区&#xff1a;三、环形缓冲区示例代码&#xff1a;总结一. 什么是环形缓冲区&#xff1f; 环形缓冲区 是一段 先进先出 的循环缓冲区&#xff0c;有一定的大小&#xff0c;我们可以把它抽象理解为一块环形的内存。 …

2023年底,我要通过这5点,实现博客访问量500W

说实话&#xff0c;这真的是一个非常高远的flag&#xff0c;因为我目前只有35W&#xff0c;但根据我2个月前还是12W的访问量&#xff0c;我觉得我还是可以拼一把的&#xff0c;在这里我想向大家分享一下我的计划&#xff0c;如何达成2023年底&#xff0c;博客访问量达到500W的K…

java 微服务之docker基础入门 docker部署 镜像相关命令 容器命令 数据卷 DockerCompose Docker镜像仓库

初识Docker 项目部署的问题 什么是Docker 不同环境的操作系统不同&#xff0c;Docker如何解决&#xff1f;我们先来了解下操作系统结构 Docker与虚拟机 虚拟机是在一个系统内&#xff0c;运行另外一个系统 镜像和容器 镜像&#xff08;Image&#xff09;&#xff1a;Docker将…

通讯电平转换电路中的经典设计

今天给大家分享几个通讯电平转换电路。 有初学者问&#xff1a;什么是电平转换&#xff1f;举个例子&#xff0c;比如下面这个电路&#xff1a; 单片机的工作电压是5V&#xff0c;蓝牙模块的工作电压是3.3V&#xff0c;两者之间要进行通讯&#xff0c;TXD和RXD引脚就要进行连接…

ClickHouse 挺快,esProc SPL 更快

开源分析数据库ClickHouse以快著称&#xff0c;真的如此吗&#xff1f;我们通过对比测试来验证一下。 ClickHouse vs Oracle 先用ClickHouse&#xff08;简称CH&#xff09;、Oracle数据库&#xff08;简称ORA&#xff09;一起在相同的软硬件环境下做对比测试。测试基准使用国…

Spring Boot 3.0横空出世,快来看看是不是该升级了

文章目录简介对JAVA17和JAVA19的支持recordText BlocksSwitch Expressionsinstanceof模式匹配Sealed Classes and Interfaces迁移到Jakarta EEGraalVM Native Image Support对Micrometer的支持其他的一些改动简介 Spring boot 3.0于2022年11月正式发布了&#xff0c;这次的发布…