Union-Find算法

声明

本文章的内容来源于《abuladong的算法小抄》,为了加强自己的记忆,写本文章梳理学习内容。

算法简介

本算法为通常说的并查集算法,主要解决图论中“动态连通性”问题。数据结构(清华版)课本上有一节专门讲了这个算法。

问题

一个图(无向图)有5个节点(1~5),他们不是相互连通的,那么我们认为连通分量为5个。
在此我们可以实现Union-Find算法的API:

class UnionFind{
public:
    // 将p节点和q节点连通
    void union(int p, int q);
    //判断p节点和q节点是否连通
    bool connect(int p, int q);
    //返回图中有多少个连通分量
    int count();
}

这里得连通是建立在无向图之上的。
如果现在调用union(1,2),那么节点0和节点1被连通,连通分量就变成4个。
再调用union(2,3),那么这时1,2,3节点都被连通,再调用connected(1,3)也会返回true,连通分量变成8个。
由此可见,本算法的关键在于union和connected函数的效率。

基本思路

模型:使用森林来表示图的动态连通性
数据结构:数组实现这棵树
表示连通性:设定数的每个节点用一个指针指向其父节点,如果是根节点,那么其父节点就是他自己。
代码实现:

#include<iostream>
#include<vector>
using namespace std;

class UnionFind{
private:
    //记录连通分量
    int count;
    //记录其父节点
    vector<int> parent;
public:
    //构造函数
    UnionFind(int n){
        //开始都不连通
        this->count = n;
        //初始化父节点都是指向自己
        this->parent.resize(n);
        
        for(int i = 0; i < n; i++) this->parent[i] = i;
    }

    void Union(int p, int q){
        //找根节点
        int rootp = find(p);
        int rootq = find(q);
        //如果根节点相同 说明两个节点是连通的
        if(rootq == rootp) return;

        
        //跟节点连接起来
        // 下面两种连接都可以
        // this->parent[rootq] = rootp;
        this->parent[rootp] = rootq;
        this->cout--;
    }
    //找根节点
    int find(int x){
        while(x != this->parent[x]) x = this->parent[x];
        return x;
    }
    //返回连通分量
    int count(){
        return this->count;
    }

    bool Connected(int p, int q){
        int rootq = find(q);
        int rootp = find(p);
        //根节点相同 说明是连通的
        return rootp == rootq;
    }
};

如果两个节点被连通,则让其中一个节点的根节点接到另一个节点上。
如果两个节点拥有相同的根节点,说明这两个节点是连通的。

时间复杂度:find,union,connected的最坏时间复杂度是O(N)
如果数据量巨大,这种复杂度是不可以接受的,主要的原因是我们构建的树模型存在不平衡现象

平衡优化

出现不平衡问题主要出现在union现象
我们只是简单粗暴的将一个树的根节点接到另一棵树的根节点下面,这样会产生极度不平衡现象。
将小一些的树接到大一些树的下面,这样就可以更加平衡一些。我们定一个变量,记录每一颗树的节点数目。

class UnionFind{
private:
    //记录连通分量
    int count;
    //记录其父节点
    vector<int> parent;

    //增加数组,记录树包含的节点数
    vector<int> size;
public:
    //构造函数
    UnionFind(int n){
        //开始都不连通
        this->count = n;
        //初始化父节点都是指向自己
        this->parent.resize(n);
        this->size.resize(n);
        for(int i = 0; i < n; i++) {
            this->parent[i] = i;
            this->size[i] = 1;
        }
    }

    void Union(int p, int q){
        //找根节点
        int rootp = find(p);
        int rootq = find(q);
        //如果根节点相同 说明两个节点是连通的
        if(rootq == rootp) return;

        
        //跟节点连接起来
        // 下面两种连接都可以
        // this->parent[rootq] = rootp;  //q树接到p树上 ***注意
        // this->parent[rootp] = rootq;
        //小树接到大树的下面
        if(this->size[rootp] > this->size[rootq]){
            //p树的节点多,为大树, q树接到p树上 ***注意
            this->parent[rootq] = rootp;
            this->size[rootp] += this->size[rootq]
        }
        else{
            this->parent[rootp] = rootq;
            this->size[rootq] += this->size[rootp]
        }
        this->cout--;
    }
 //其他函数不变
}

此时 find、union、connected的时间复杂度标成了O(Nlog(N))
注意 this->parent[rootq] = rootp; 是q树接到p树上,在写次博客的时候差点把自己弄晕了。

路径压缩

我们可以进行路径压缩,让树的高度保持常数,这样find就可以用O(1)的时间复杂度找到某个根节点,相应的connected和union函数也降到了O(1)

    int find(int x){
        // while(x != this->parent[x]) x = this->parent[x];
        while(parent[x] != x){
        	// 只需要加入这一样即可
            parent[x] = parent[parent[x]];
            x = parent[x];
        }
        return x;
    }

有了压缩路径,那么记录树节点个数的size数组还要么?
可以不要,但是加上会效率高一些,可以让树更平衡一些。所以,路径压缩和重量平衡都用上是最好的选择。

完成代码

#include<iostream>
#include<vector>
using namespace std;

class UnionFind{
private:
    //记录连通分量
    int count;
    //记录其父节点
    vector<int> parent;

    //增加数组,记录树包含的节点数
    vector<int> size;
public:
    //构造函数
    UnionFind(int n){
        //开始都不连通
        this->count = n;
        //初始化父节点都是指向自己
        this->parent.resize(n);
        this->size.resize(n);
        for(int i = 0; i < n; i++) {
            this->parent[i] = i;
            this->size[i] = 1;
        }
    }

    void Union(int p, int q){
        //找根节点
        int rootp = find(p);
        int rootq = find(q);
        //如果根节点相同 说明两个节点是连通的
        if(rootq == rootp) return;

        
        //跟节点连接起来
        // 下面两种连接都可以
        // this->parent[rootq] = rootp;  //q树接到p树上 ***注意
        // this->parent[rootp] = rootq;
        //小树接到大树的下面
        if(this->size[rootp] > this->size[rootq]){
            //p树的节点多,为大树, q树接到p树上 ***注意
            this->parent[rootq] = rootp;
            this->size[rootp] += this->size[rootq]
        }
        else{
            this->parent[rootp] = rootq;
            this->size[rootq] += this->size[rootp]
        }
        this->cout--;
    }
    //找根节点
    int find(int x){
        // while(x != this->parent[x]) x = this->parent[x];
        while(parent[x] != x){
            parent[x] = parent[parent[x]];
            x = parent[x];
        }
        return x;
    }
    //返回连通分量
    int count(){
        return this->count;
    }

    bool Connected(int p, int q){
        int rootq = find(q);
        int rootp = find(p);
        //根节点相同 说明是连通的
        return rootp == rootq;
    }
};

int main(){

    UnionFind A = UnionFind(10);
    return 0;
}

除了构造函数的时间复杂度是O(N),其他的时间复杂度都是O(1)

结束

如果有读者需要图来解释的话,等我忙完这段时间再补上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源 数据结构、算法相关的资源

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值