欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

考研408数据结构复习指南:深入理解并查集详解

最编程 2024-07-30 08:38:05
...

这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

考研倒计时:47天

参考资料: 王道数据结构考研

并查集是2022年408新增考点

本篇简要总结并查集的代码实现以及应用. 考纲要求到应用层面,所以代码需要会实现!

逻辑结构

元素之间为集合关系,主要有查操作和并操作,存储结构可以采用双亲表示法。 [图片来源网络]

image.png

代码实现

初始化

#define SIZE 13
int UFSets[SIZE];  //集合元素数组

//初始化并查集
void Initial(int S[]){
    for(int i=0;i<SIZE;i++){
        S[i] = -1;
    }
}

”查“操作,返回x所属的根结点。

最坏时间复杂度为O(n)

int Find(int S[],int x){//找x所属的集合
    while(S[x]>0){
        x=S[x];         //循环寻址x的根
    }
    return x;           //根的s[]小于0,返回根结点的编号
}

”并“操作

时间复杂度为O(1)

void Union(int S[],int Root1,int Root2){
    if(Root1 != Root2){//要求Root1和Root2是不同的集合
        S[Root2] = Root1;  //将根Root2连接到另一根Root1的下面
    }
}

优化思路:每次合并的操作让树尽量不长太高。

  • 用根结点的绝对值表示树的结点总数
  • 让小树合并到大树

优化后的”并“操作

void Union(int S[],int Root1,int Root2){
    if(Root1 == Root2) return;
    if(S[Root2]>S[Root1]){   //Root2的结点数更少
        S[Root1] += S[Root2];  //累加结点个数
        S[Root2] = Root1;      //小树合并到大树
    }esle{
        S[Root2] += S[Root1];  //累加结点个数
        S[Root1] = Root2;      //小树合并到大树
    }
}

该方法构造的树高不超过log2n向下取整+1

所以优化后,Find操作最坏时间复杂度:O(log2n)

应用

判断图的连通分量数

遍历无向图的邻接矩阵的上三角区域,利用并查集进行合并,连通分量会被化为一个集合。

最后遍历并查集,有多少个小于0的值即为连通分量的个数

int ComponentCount(int g[5][5]){
    //g[5][5]二维数组表示的邻接矩阵
    int S[5];
    for(int i=0;i<5;i++) S[i]=-1;//初始化并查集
    for(int i=0;i<5;i++){//遍历各条边,无向图遍历上三角型。
        for(int j=i+1;j<5;j++){
            if(g[i][j]>0){//结点i、j有边
                int iRoot = Find(S,i);
                int jRoot = Find(S,j);
                if(iRoot != jRoot){ //i,j并入同一集合
                    Union(S,iRoot,jRoot);
                }
            }
        }
    }
    //统计有几个连通分量
    int count=0;
    for(int i=0;i<5;i++){
        if(S[i]<0){
            count++;
        }
    }
    return count;
}

判断图是否有环

已经连通的子图中,但凡再多出来一条边,这个子图就一定有环。

代码在上面的基础上加一条判断即可。

             if(iRoot != jRoot){ //i,j并入同一集合
                    Union(S,iRoot,jRoot);
                }
             else{//!!!i,j原本在同一个集合
                 return 1;//在一个连通子图中,再多一条边,必有环。
             }
                

实现Kruskal算法

各条边按权值递增排序,依次处理各条边。

通过Find操作确定一条边所连接的两个顶点是否属于同一个集合,如果不属于同一个集合,则将这条边加入生成树,并将两个点所属集合Union。

补充并查集优化--压缩路径

Find操作:先找到根结点,再将查找路径上所有结点都挂载到根结点上,下一次再查该节点只需要O(1),可以使树高不超过a(n)增长很慢的函数。

比如下图(来源网络)就是一种压缩路径的表现

image.png

int Find(int S[],int x){
    int root = x;
    while(S[root]>=0)  root=S[root];//循环找到根
    while(x!=root){  //压缩路径
        int t=S[x];  //t指向x的父节点
        S[x] = root; //x直接挂载到根结点
        x=t;
    }
    return root;       //返回根结点的编号
}

最后总结如下:

image.png


部分内容待补充完善~

如有误,请多指正!