合并集合问题通常涉及到将两个或多个不相交的集合合并成一个集合。在计算机科学中,这通常通过并查集(Disjoint-set)数据结构来实现,也称为联合查找(Union-Find)。
基本思想:
- 集合的表示:每个集合由一个代表元素(或称为集合的根)表示,集合中的所有元素都与这个代表元素相连。
- 合并操作:要合并两个集合,只需将一个集合的代表元素指向另一个集合的代表元素,从而将两个集合连接起来,形成一个新的集合。
操作步骤:
- 初始化:开始时,每个元素自成一个集合,每个元素的代表元素是其自身。
- 查找(Find):
- 目标:确定元素所属的集合。
- 过程:从给定元素开始,沿着代表元素链向上查找,直到找到代表元素的链的顶端(即根节点)。
- 路径压缩:在查找过程中,将查找路径上的所有节点直接链接到根节点,以优化后续查找操作。
- 合并(Union):
- 目标:将两个集合合并为一个集合。
- 过程:找到两个元素的代表元素,然后将其中一个代表元素指向另一个代表元素,完成合并。
- 优化:
- 根据具体实现,可能会有额外的优化步骤,如按秩合并(将较小的集合合并到较大的集合中),以减少树的高度,提高查找效率。
并查集是一种非常实用的数据结构,它在图论中用于处理连通性问题,如判断无向图的连通分量、最小生成树、网络流问题等。通过并查集,合并操作可以在接近线性的时间复杂度内完成,大大提高了处理效率。
输入格式
- 第一行:包含两个整数
n(1<= n <= 100000)
和m(1 <= m <= 100000)
。n
表示元素的数量,即编号的范围从 1 到n
。m
表示操作的数量,即接下来会有m
个操作指令。
接下来 m
行
- 每行包含一个操作指令,指令有两种形式:
M a b
:表示将编号为a
和b
的两个数所在的集合合并。Q a b
:表示询问编号为a
和b
的两个数是否在同一个集合中。
输出格式
- 对于每个询问指令
Q a b
,输出一个结果。- 如果编号为
a
和b
的两个数在同一个集合内,则输出Yes
。 - 否则,输出
No
。
- 如果编号为
- 每个结果占一行。
输入样例:
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; // 定义常量N,表示集合的最大元素数量
// p数组用于存储并查集的父节点信息
int p[N];
// find函数用于查询元素x所属的集合的代表元素
int find(int x)
{
if(x != p[x]) // 如果x不是其自身的代表元素
p[x] = find(p[x]); // 递归查询其父节点,路径压缩
return p[x]; // 返回x所属集合的代表元素
}
int main()
{
int n, m; // n表示元素数量,m表示操作数量
cin >> n >> m;
for(int i = 1; i <= n; i++) // 初始化并查集
p[i] = i; // 每个元素初始时自成一个集合,其代表元素是自己
while(m--) // 处理所有操作
{
string op; // 操作类型
int a, b; // 操作涉及的两个元素
cin >> op >> a >> b;
a = find(a), b = find(b); // 分别查询a和b所属集合的代表元素
if(op == "M") // 如果操作类型为"M",表示合并操作
{
if(a != b) p[a] = b; // 如果a和b不是同一集合的,则将a所属集合合并到b所属集合
}
else { // 否则是查询操作
if(a == b) puts("Yes"); // 如果a和b是同一集合的,则输出"Yes"
else puts("No"); // 否则输出"No"
}
}
return 0; // 程序结束
}
总结:
合并集合是一种处理不相交集合(即每个元素属于且仅属于一个集合)之间的合并与查询操作的算法,通常通过并查集数据结构实现。它允许我们高效地确定两个元素是否属于同一集合,以及将两个集合合并为一个集合。在并查集中,每个集合由一个代表元素标识,而合并操作通过将一个集合的代表元素指向另一个集合的代表元素来完成。这种结构通过路径压缩和按秩合并等优化技术,能够以接近线性的时间复杂度执行操作,适用于解决图论中的连通性问题、最小生成树、网络流等多种计算问题。