并查集操作
1.将两个集合合并
2.询问两个元素是否在一个集合当中
基本原理
基本原理:每一个集合用一颗树来表示,树根的编号就是整个集合的编号,每个节点存储它的父节点,p[x]表示x的父节点
常见考点
问题1:如何判断树根:if(p[x]==x)
问题2:如何求x的集合编号:while(p[x]!=x) x=p[x];只有根节点的父节点是自己
问题3:如何合并两个集合:px是x的集合编号,py是y的集合编号 p[x]=y
问题4:为什么说路径优化之后的时间复杂度是近似于O(1)的
查询优化
对于问题2来说普通版本就是一直遍历直到根节点,但是这样的时间复杂度也是很高的,那么我们做路径压缩,在一次遍历的时候我把路径上的所有结点的父节点直接设置为根节点(这里已经不能叫父节点了应该叫祖先结点),这样的话我们在后面还要考虑寻找这个集合里面的元素是属于哪个集合的话时间复杂度就变成O(1)了,但是对于没有经历过第一次查询的字符串来说,时间复杂度还是o(n)因此综上所述,在我们查询一个结点属于哪个集合的时候时间复杂度近似O(1),之所以近似就是存在第一次很浪费时间的遍历寻找
代码实现
一共有n个数,编号是1~n,最开始每个数各自在一个集合
现在要进行m个操作,操作有两种:
1."M a b",将编号为a和b的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作
2:"Q a b",询问编号为a和b的两个数是否在同一个集合中,是则输出yes不是则输出no
#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
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];
int a, b;
scanf("%s%d%d", op, &a, &b);
if (op[0] == 'M')
p[find(a)] = find(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”中的一种。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
int p[N];
int n, m;
int size[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; //初始化
size[i] = 1;
}
while (m--) {
char op[5];
int a, b;
scanf("%s%", op);
if (op[0] == 'C')//c表示将a和b连接在一起
{
scanf("%d%d",&a,&b);
if(find[a]==find[b])
continue;
size[find[b]]+=size[find[a]];//每一次连接就把size加到b中
p[find[a]]=find[b];
}
else if(op[1]=='1'){
//询问点a和点b是否在同一个连通块中,a和b可能相等
scanf("%d%d",&a,&b);
if(find[a]==find[b])
puts("yes");
else
puts("no");
}
else{//询问a所在集合的结点个数
scanf("%d%d",&a);
printf("%d\n",size[find[a]]);
}
}
return 0;
}