并查集算法模版
并查集
并查集常见的操作
1.查询两个元素是否在同一个集合之中
2.合并两个集合
3.查询集合之中有多少个元素
模板题1
路径压缩优化(重点)
在并查集算法中,有一个p[N]数组,用来存储该节点的节点的编号
每一个集合都有唯一的一个编号
初始化,自身为一个集合,父节点的编号指向自己
r(int i = 1; i <= n; i++) p[i] = i;
路径压缩就是让元素都指向祖宗节点,避免循环找一个集合的祖宗编号
大大降低了时间复杂度
int find(int x){
//返回x的祖先节点 + 路径压缩
//祖先节点的父节点是自己本身
if(p[x] != x){
//将x的父亲置为x父亲的祖先节点,实现路径的压缩
p[x] = find(p[x]);
}
return p[x];
}
针对 x = 1
find(1) p[1] = 2 p[1] = find(2) find(2) p[2] = 3 p[2] = find(3)
find(3) p[3] = 4 p[3] = find(4) find(4) p[4] = 4 将p[4]返回退到上一层 find(3) p[3] = 4 p[3] = 4 将p[3]返回 退到上一层 find(2) p[2] = 3 p[2]
= 4 将p[2]返回 退到上一层 find(1) p[1] = 2 p[1] = 4 将p[1]返回至此,我们发现所有的1,2,3的父节点全部置为了4,实现路径压缩;同时也实现了1的父节点的返回
这里借用了acwing中派大星的梦想的图解
// /*
// 并查集有2个基本操作
// 1.合并两个集合
// 2.查询两个集合是否在同一个集合之中
// 以树的形式,每个集合都有一个独一无二的编号,编号在根节点上(p[x] == x)
// 每个结点都有个父节点 p[x] ,父节点初始化(都指向自己)
// 合并两个集合 :
// p[x] = y;(就将一个集合(树),插到另一个集合中,成为其祖宗结点的子孩子)
// 但是要先找到祖宗结点的编号 while(p[x] != x) x = p[x];
// 这样时间复杂度比较大,路径压缩(让该节点,都成为祖宗节点的子孩子)
// */
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1e5 + 10;
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;
//刚开始p[i] = i, 合并几个集合之后,p[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);
else
{
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}
模板题2
//1.连边操作,相当于并查集的合并集合
//2.2个点是否在一个连通块--->判断两个是否在一个集合当中
//3.连通块中的数量-->集合中元素的个数,用cnt[N]存储,下标表示集合的祖宗编号
//操作三注意点:size数组也要进行初始化为1,合并集合的时候,size数组也合并
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1e5 + 10;
int p[N], cnt[N];
int find(int x)
{
if(p[x] != x)
{
p[x] = find(p[x]);
}
return p[x];
}
int n, m;
int main()
{
scanf("%d %d", &n, &m);
//初始化集合和cnt数组
for(int i = 1; i <= n; i++)
{
p[i] = i;
cnt[i] = 1;
}
while(m--)
{
char op[2];
scanf("%s", op);
if(*op == 'C')
{
int a, b;
scanf("%d %d", &a, &b);
int c = find(a), d = find(b);
//合并的时候,先判断是否在有一个集合
if(c != d)
{
//注意:这两步顺序不要写反了
p[c] = d;
//a的祖宗等于b的祖宗,a 合并到b中去
//所以是b的数量 += a的数量
//这里由于先合并了的,a, b祖宗编号已经改变
//不能用cnt[find(b)] += cnt[find(a)]算
//用之前记录的算
cnt[d] += cnt[c];
//当然先算cnt,再合并可以
//cnt[find(b)] += cnt[find(a)];
//p[c] = d;
}
// a = find(a), b = find(b);
// if (a != b)
// {
// p[a] = b;
// cnt[b] += cnt[a];
// }
}
if(op[1] == '1')
{
int a, b;
scanf("%d %d", &a, &b);
if(find(a) == find(b)) printf("Yes\n");
else printf("No\n");
}
if(op[1] == '2')
{
int a;
scanf("%d", &a);
printf("%d\n", cnt[find(a)]);
}
}
return 0;
}
文章评论