并查集算法模板

并查集

并查集常见的操作
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;
}
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温柔了岁月.c

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值