并查集C++实现

并查集是什么

是一种含有合并和查找功能的数据结构,用于处理一些不交集的合并和查询问题。主要含有两个功能:判断任意两个元素是否属于同一个集合;按照要求合并不同集合。

并查集的基本操作
查找:

给出一个元素,查找元素属于哪个集合。步骤:不断向上查找,直到找到他的根节点,之后根据根节点是否相同判断两个元素是否属于同一个集合。查询操作完成后,并查集结构还会进行路径压缩操作,就是将查询节点到根节点之间的所有节点都指向根节点,可以有效限制因不断合并造成部分区域不匀称,使其保持较低的树高。

合并:

将两个子集合并成同一个集合。步骤:将一棵树作为另一棵树的子树,从而使两棵树变为一颗更大的树。

哈希表格式

使用哈希表来存放每个元素的父节点,键来存放元素节点,值为父亲节点。再用哈希表存放每个元素的size,换言之就是存放每个集合的节点数。在查找过程中用栈存放过程节点,最终弹栈将节点直接连接在集合的头节点上。

class UnionFind{
public:
    //并查集的构造函数,将传入的数组元素添加到哈希map中,每个元素都是一个集合,所以对应的value就是本身,sizemap用来存放每个节点是集合头节点时的元素个数,初始化时都是1
    UnionFind(vector<int>&arr){
        for(int i:arr){
            parent[i]=i;
            mapsize[i]=1;
        }
    }
    //查找某元素所在集合的头节点,同时将查找过程压入栈中,
    int FindFather(int num){
        stack<int>path;
        //查找当前节点的父节点是否是本身,如果是,就说明当前节点就是集合的头节点;如果不是,就将父节点的值赋值给当前节点,然后继续向上查找,直到找到集合的头节点,循环退出。
        while(num!=parent[num]){
            path.push(num);
            num=parent[num];
        }
        //路径压缩:将查询路径上的所有节点全都直接将集合头节点作为父节点
        while(!path.empty()){
            parent[path.top()]=num;
            path.pop();
        }
        return num;
    }
    //合并两个元素所在的集合
    void un(int m,int n){
        int f1=FindFather(m);
        int f2=FindFather(n);//首先查找两个元素所在的集合头节点,根据此判断两个元素是否在同一个集合
        if(f1!=f2){
        //若不在一个集合中,就将规模更小的集合接在较大集合的头节点上,同时将对应的组合后的集合size更新
            if(sizemap[f1]>=sizemap[f2]){
                sizemap[f1]+=sizemap[f2];
                parent[f2]=f1;
            }else{
                sizemap[f2]+=sizemap[f1];
                parent[f1]=f2;
            }
        }
    }
    //返回当前并查集中含有多少个集合
    int sets(){
        return sizemap.size();
    }
private:
    map<int,int>parent;//存放每个元素和该元素的父节点
    map<int,int>mapsize;//存放以当前节点为头节点的集合含有多少个元素
};
数组格式

使用数组存放并查集中的父子节点关系和存放每个节点作为集合头节点时的元素个数。使用辅助数组替代栈来完成路径压缩,整体的时间复杂度与用哈希表写法一样,但是常数时间优化较大。

class UnionFind{
public:
    //并查集构造函数,接受一个数组作为参数
    UnionFind(vector<int>&arr){
        //设置数组的长度
        int N=arr.size();
        //此时数组的长度都是受传入数组中的最大值限制
        parent.resize(INT_MAX);
        sizemap.resize(INT_MAX);
        help.resize(INT_MAX);
        sets=N;
        //遍历整个数组,将父子关系填入到parent中,将对应位置sizemap设为1
        for(int i=0;i<N;i++){
            parent[arr[i]]=arr[i];
            sizemap[arr[i]]=1;
        }
    }
    //使用辅助数组help存放路径上的节点,通过变量hi记录弹栈位置
    int FindFather(int num){
        int hi=0;
        while(num!=parent[num]){
            help[hi++]=num;
            num=parent[num];
        }
        for(hi--;hi>=0;hi--){
            parent[help[hi]]=num;
        }
        return num;
    }
    //合并两个元素所在的集合,合并完成后并查集的集合数-1
    void un(int n,int m){
        int f1=FindFather(n);
        int f2=FindFather(m);
        if(f1!=f2){
            if(sizemap[f1]>=sizemap[f2]){
                sizemap[f1]+=sizemap[f2];
                parent[f2]=f1;
            }else{
                sizemap[f2]+=sizemap[f1];
                parent[f1]=f2;
            }
        }
        sets--;
    }
    //返回当前并查集的集合数
    int sts(){
        return sets;
    }
private:
    vector<int>parent;
    vector<int>sizemap;
    vector<int>help;
    int sts;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值