作用
快速的处理以下问题:【近乎O(1)的时间完成】
1.将两个集合合并
2.询问两个元素是否在一个集合中
用树的形式维护集合
基本原理
每一个集合用一棵树表示
每一个集合的编号就是根结点的编号,对于每一个结点,都存储其父结点,p[x]表示x的父结点,即p[x]=a表示编号为x的结点的父结点的编号为a
求某个点属于哪个集合时,就先找其父结点,如果其父结点不是根结点,那么就继续找其父结点的父结点,直到找到其根结点为止
问题1
如何判断树根:if(p[x]==x)
问题2
如何求x的集合编号:while(p[x]!=x) x=p[x];【只要x不是树根,就一直往上走,直到找到树根为止】
该步骤时间复杂度仍然很高,需要进行以下优化:
路径压缩:一旦找到根结点,就会把整个路径上所有点都指向根结点。【基本O(1)】
安秩合并【一般不用】
问题3
如何合并两个集合:px是x集合的集合编号【即x集合的根节点的编号是px】,py是y集合的集合编号【即y集合的根节点的编号是py】 p[px]=py或p[py]=px
代码实现
int find(int x) {//返回x所在集合的编号+路径压缩
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
例题------合并集合
一共有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
代码
#include<iostream>
using namespace std;
const int N = 100010;
int p[N];//存储父结点
int n, m;
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;
while (m--) {
char op[2];//因为在使用scanf进行读取时,若读取单个字符会读取到空格、回车等其他字符,使用scanf读取字符串时可忽略空格、回车等其他字符
int a, b;
scanf("%s%d%d", op, &a, &b);
if (op[0] == 'M')
p[find(a)] = find(b);//合并
//find(a)返回a的祖宗结点,find(b)返回b的祖宗结点,让a的祖宗结点的父结点等于b的祖宗结点
else {
if (find(a) == find(b))
puts("Yes");
else
puts("No");
}
}
return 0;
}
例题------连通块中点的数量
给定一个包含n个点(编号为1~n)的无向图,初始时图中没有边。
现在要进行m个操作,操作共有三种:
"C a b",在点a和点b之间连一条边,a和b可能相等;
"Q1 a b",询问点a和点b是否在同一个连通块中,a和b可能相等;
"Q2 a",询问点a所在连通块中点的数量;
输入格式
第一行输入整数n和m。
接下来m行,每行包含一个操作指令,指令为"C a b","Q1 a b"或"Q2 a"中的一种。
输出格式
对于每个询问指令"Q1 a b",如果a和b在同一个连通块中,则输出"Yes",否则输出"No"。
对于每个询问指令"Q2 a",输出一个整数表示点a所在连通块中点的数量
每个结果占一行。
数据范围
1≤n,m≤10^5
输入样例
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], size1[N];//存储父结点
int n, m;
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;
size1[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;
//特判:如果两个数已经在一个集合,就不用再次合并,不然会使得元素中的元素个数翻倍
size1[find(b)] += size1[find(a)];//b中元素数量为其本身加上a中元素数量
p[find(a)] = find(b);//合并,将a合并到b中
}
else if (op[1] == '1') {
scanf("%d%d", &a, &b);
if (find(a) == find(b))
puts("Yes");
else
puts("No");
}
else {
scanf("%d", &a);
printf("%d\n", size1[find(a)]);
}
}
return 0;
}