并查集功能:近乎 O(1)
- 将两个集合合并
- 询问两个元素是否在一个集合当中
如果上述两个功能用暴力做法:
- 询问两个元素是否在一个集合中
// 时间复杂度是O(1)的
belong[x] = a
if(belong[x] == belong[y])
- 将两个集合合并
这时暴力做法就需要将小集合合并到大集合中,操作效率低,这里可以用并查集优化
并查集原理:
每一个集合用一颗树来维护。有以下规定。
- 根节点是集合代表元素,根节点的标号就是集合的编号
- 记录每一个点都存储父节点是谁 p[x] 表示x 的父节点
问题1 :如何判断树跟 : if (p[x] == x)
问题2 :如何求x的集合编号 : while(p[x] != x) x = p[x];
, 只要不是树根,一直往上走
问题3 : 如何合并两个集合 : 根节点加一条线即可,p[x] 是x集合编号,p[y] 是y的集合编号。p[x] = y
.
并查集还有一个优化:
当集合中的一个点x 从根节点需要往下查找3次,查找第一遍后,可以修改这条路径上所有点全部指向根节点,后面查找则可以一次查找成功,所以这里的时间复杂度近乎O(1).
题目
一共有 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
核心:
find 函数
code:
#include <iostream>
using namespace std;
const int N = 100010;
int p[N], n, m; //p[] 是存储父节点 当 p[x] == x x就是树根
// 也是集合编号
int find(int x) // 返回的是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; // 所以节点指向自己
while(m --)
{
char op[2];
int a, b;
scanf("%s%d%d", op, &a, &b);
if(*op == 'M')
{
//p[find(a)] = find(b);
p[find(b)] = find(a); // a 的祖宗节点 = b的祖宗节点,两个集合加了一条线
}
else
{
if(find(a) == find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}