小白学算法(13)并查集(找老大)

114 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情

并查集

“并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。”

1.将两个集合合并

2.询问两个元素是否在一个集合中

原理

每个集合用一个树进行表示,树的根节点就是该集合的编号,

每个节点存储它的父节点,p[x]=父节点

一直追溯到它的父节点,判断是否相同,则是同一集合

问题1:如何判断树根

if(p[x]==x)

问题2:如何求X的集合编号

问题3:如何合并两个集合

p[x]是x的集合编号,p[y]是y的集合编号,p[x]=y

1.模板介绍

1)初始化

int p[N];//从来表示每个集合的根节点,老祖宗!

//一开始都是不相交的集合,每个数自成集合,根节点肯定就是自己
for(int i=1;i<=n;i++){
    p[i]=i;
}

2)查找老大节点

int find(int x){
    //我不是帮派的老大,那肯定头上还有老大,那就是继续向上查找
    //这里有个路径压缩问题,find(p[x])进行了一个递归,所以最终结果就是根节点,所以此时的节点就直接指向了根节点,减少了中间节点的遍历时间
    //就比如你和你老大之间还有个二当家,那么不管他,我直接和老大对话
    if(p[x]!=x)p[x]=find(p[x]);
    return p[x];
}

优化:路劲压缩,如果找到根节点,那么子节点全部指向根节点,这样下次查找的效率就成为了O(1)

3)合并两个集合

//如果合并两大家族呢? 那就是认同一个祖宗,你老大和我老大是同一个,我们就是兄弟了
p[find(x)]=find(y)

4)实操演练

836. 合并集合 - AcWing题库

一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。

现在要进行 m 个操作,操作共有两种:

  1. M a b,将编号为 a和b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. Q a b,询问编号为 a 和b 的两个数是否在同一个集合中;
#include<iostream>
using namespace std;
const int N=1e5+10;
int n,m;
int p[N];
//不止有返回根节点的用户,还进行了路径压缩
int find(int x)
{
    //如果只返回根节点,不进行路径压缩,那么导致超过内存限制,进行路径压缩的原因,我们只需要判断集合根节点是否相同确定两个是否为同一个集合
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
int main()
{
    cin>>n>>m;
    //初始化 每个集合只有自己
    for(int i=1;i<=n;i++)
    {
        p[i]=i;
    }
    while(m--)
    {
        char op;
        int a,b;
        cin>>op>>a>>b;
        //find返回根节点
        if(op=='M')p[find(a)]=find(b);
        else if(find(a)==find(b))cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
    return 0;
}