并查集入门讲解

博客目录

一、引入

并查集的功能如下:

如果a和b是舍友,b和c是舍友,则我们认定a和c也是舍友(当然在离散数学中这是错误的,为了引入我们假定自己和自己也是舍友)。也就是说有n个人,给出m个元组(a,b)表示a和b是舍友,现在有许多查询:请你判断任意两个人之间是否有舍友关系。

复杂度来讲,建立和查询加起来大概是O(n)再加上一个常数,效率也比较高。

并查集还可以解决以下问题:

问:如何判断一个简单无向图是否为连通图(图一般会以二元组序列形式给出)

  • dfs/bfs直接搜索(空间复杂度为O(n*n) (邻接矩阵存图)、O(n)(邻接表存图,但是vector会额外占用大量时间),时间复杂度为O(n),因为如果联通,每个点都会被搜索到有且仅有一遍)
  • 并查集
  • ps:并查集空间复杂度只有O(n),代码更加简洁,且查询越多,平均时间复杂度越低。查询极多的时候可以认为查询的复杂度为O(1)。

二、直接讲最优的方法

并查集+优化路径压缩

以上问题可看做分组问题,或者连通图问题

假设有n个人编号1~n,则

1.建立并查集(初始化):

      我们以n个人建树,第i个树现在只有i这个root节点。设pre[x]表示x的父亲节点是谁,初始pre[x]=x (假设root节点的父亲还是root,这根linux文件系统有点像)

int const n=1000+1;//总人数
int pre[maxn];//树根
void build{
    for(int i=1;i<=n;i++){
        pre[i]=i;
    }
}
 

2.建立关系

给出m个关系a,b,表示a和b属于同一组,也就是将两个人归并到一个组的操作,就是把b所在的树挂到a的树上,为了减小树的高度和方便,我们直接把a所在的树挂到b的根上,使a和b同在一棵树上。

当然,如果a和b本来就同在一棵树上,那就不需要合并这个操作了。而判断是否在同一棵树上的依据是是否有同一个根。

我们设find(x)表示x所在树的根节点,那么合并操作代码如下:

void combine(int a,int b){
    if(find(a)==find(b))//是否在同一棵树上
        return;
    pre[find(a)]=find(b);
}

3.查找root

查找root就是find函数的功能,而find(x)函数的实现,则是顺着pre[x]函数一直回溯,直到pre[x]==x就找到root了,(根节点的父亲还是自己)。

回溯次数跟树的高度有关,为了优化,还需要采用优化路径压缩,减小树的高度。也就是在回溯的过程中,不断把当前节点重新挂载到上一层祖先上,只要保证他们的root不变,其他的父亲关系都不影响大局。用代码就是pre[x]=find(pre[x]);(令x的父亲等于爷爷,这就降低了树的高度而不改变共同的root祖先)

int find(int x){
    if(pre[x]==x)//如果已经是根节点
        return x;
    return pre[x]=find(pre[x]); 
}

4.判断两者是否在同一组内

判断两者在同一组内的依据就是判断两者是否拥有同一个祖先(之前已经说过了)

bool query(int a,int b){
    return find(a)==find(b); 
}

完整版并查集代码:(我没测试过,不过写了太多遍了应该不会错)

#include<bits/stdc++.h>
using namespace std; 
int const maxn=1000+1;//总人数
int pre[maxn];//树根
void build(int n){
    for(int i=1;i<=n;i++){
        pre[i]=i;
    }
}
int find(int x){
    if(pre[x]==x)//如果已经是根节点
        return x;
    return pre[x]=find(pre[x]); 
}
void combine(int a,int b){
    if(find(a)==find(b))
        return;
    pre[find(a)]=find(b);
}
bool query(int a,int b){
    return find(a)==find(b); 
}
int main(){
    int a,b;
    int n,m,q;
    cin>>n>>m;//输入人数 和关系个数 
    build(n);
    for(int i=1;i<=m;i++){
        cin>>a>>b;//a和b是同学 
        combine(a,b); 
    }
    cin>>q; //输入查询次数
    for(int i=1;i<=q;i++){
        cin>>a>>b;
        if(query(a,b)){
            printf("%d和%d是同学\n",a,b); 
        }
        else
            printf("%d和%d不是同学\n",a,b);
    } 
}

猜你喜欢

转载自blog.csdn.net/GreyBtfly/article/details/81912984