数据结构:并查集UnionFind

在这里插入图片描述

并查集的基础概念

并查集主要用来解决连接问题 Connectivity Problem

  • 图中两点之间是否连接的问题

连接问题

  1. 网络中节点间的连接状态
    • 这里的网络是个抽象的概念:用户之间形成的网络
    • 社交网络、道路交通…
  2. 数学中的集合类实现

连接问题和路径问题

连接问题比路径问题要回答的问题少(只判断是否连接,不判断连接的路径)

不同算法和数据结构能获得不同信息(如果算法获得了除需要解决的问题的信息外的额外信息,则该问题所用的算法存在优化空间)

并查集的两个动作

对于一组数据,并查集主要支持两个动作:

  1. find§
  2. union(p,q)

用来回答一个问题:

  • isConnected(p,q)

Quick Find

QuickFind的find操作特别快O(1),union的时间复杂度O(N)

在这里插入图片描述

namespace UF1{

		class UnionFind{
	
		private:
			int* id;
			int count;
	
		public:
			UnionFind(int n){
					count = n;
					id = new int[n];
					for(int i = 0; i < n; i++){   // 初始化,每一个id[i]指向自己,没有合并的元素
						id[i] = i;  
				}
			}
	
			~UnionFind(){
					delete[] id;
			}
	
     // 查找p所对应的集合编号
			int find(int p){       // O(1)
					assert( p >= 0 && p < count );
					return id[p];
			}
	
			// 查找p和q是否属于同一集合
			bool isConnected(int p, int q){    // O(1)
					return find(p) == find(q);
			}
	
			// 合并p和q所属的集合
			void unionElements(int p, int q){     // O(N)
					int pID = find(p);
					int qID = find(q);
					if( pID = qID )
							return;
         // 合并过程需要遍历一遍所有元素,将两个元素的所属集合合并
					for(int i = 0; i < count; i++){     
							if(id[i] = pID)
								id[i] = qID;
					}
			}
	
		}
}

Quick Union

将每个元素,都看作一个节点
每个节点,有一个指向父亲的指针 → parent数组实现

每个元素看做一个节点
初始化状态
合并之后的状态

amespace UF2{

    class UnionFind{

    private:
        // 我们的第二版Union-Find, 使用一个数组构建一棵指向父节点的树
        // parent[i]表示第i个元素所指向的父节点
        int* parent;
        int count;  // 数据个数

    public:
        // 构造函数
        UnionFind(int count){
            parent = new int[count];
            this->count = count;
            // 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合
            for( int i = 0 ; i < count ; i ++ )
                parent[i] = i;
        }

        // 析构函数
        ~UnionFind(){
            delete[] parent;
        }

        // 查找过程, 查找元素p所对应的集合编号
        // O(h)复杂度, h为树的高度
        int find(int p){
            assert( p >= 0 && p < count );
            // 不断去查询自己的父亲节点, 直到到达根节点
            // 根节点的特点: parent[p] == p
            while( p != parent[p] )
                p = parent[p];
            return p;
        }

        // 查看元素p和元素q是否所属一个集合
        // O(h)复杂度, h为树的高度
        bool isConnected( int p , int q ){
            return find(p) == find(q);
        }

        // 合并元素p和元素q所属的集合
        // O(h)复杂度, h为树的高度
        void unionElements(int p, int q){

            int pRoot = find(p);
            int qRoot = find(q);

            if( pRoot == qRoot )
                return;

            parent[pRoot] = qRoot;
        }
    };
}

并查集的优化(union)

基于size的优化

size:集合的大小(元素个数)

之前版本的union操作,对于这种情况,union(4,9)和union(9,4)是不一样的

将少的元素集合的根节点指向元素多的那个元素的根节点,这样形成高度比较低的树

在这里插入图片描述
在这里插入图片描述

namespace UF3{

    class UnionFind{

    private:
        int* parent; // parent[i]表示第i个元素所指向的父节点
        int* sz;     // sz[i]表示以i为根的集合中元素个数
        int count;   // 数据个数

    public:
        // 构造函数
        UnionFind(int count){
            parent = new int[count];
            sz = new int[count];
            this->count = count;
            for( int i = 0 ; i < count ; i ++ ){
                parent[i] = i;
                sz[i] = 1;  // 初始时每个元素互相独立
            }
        }

        // 析构函数
        ~UnionFind(){
            delete[] parent;
            delete[] sz;
        }

        // 查找过程, 查找元素p所对应的集合编号
        // O(h)复杂度, h为树的高度
        int find(int p){
            assert( p >= 0 && p < count );
            // 不断去查询自己的父亲节点, 直到到达根节点
            // 根节点的特点: parent[p] == p
            while( p != parent[p] )
                p = parent[p];
            return p;
        }

        // 查看元素p和元素q是否所属一个集合
        // O(h)复杂度, h为树的高度
        bool isConnected( int p , int q ){
            return find(p) == find(q);
        }

        // 合并元素p和元素q所属的集合
        // O(h)复杂度, h为树的高度
        void unionElements(int p, int q){

            int pRoot = find(p);
            int qRoot = find(q);

            if( pRoot == qRoot )
                return;

            // 根据两个元素所在树的元素个数不同判断合并方向
            // 将元素个数少的集合合并到元素个数多的集合上
            if( sz[pRoot] < sz[qRoot] ){
                parent[pRoot] = qRoot;
                sz[qRoot] += sz[pRoot];
            }
            else{
                parent[qRoot] = pRoot;
                sz[pRoot] += sz[qRoot];
            }
        }
    };
}

基于rank的优化

对于基于size的极端情况:左边高度变为4,右边高度为3

rank:集合的层数(树的高度),rank[i]表示根节点为i的树的高度

在这里插入图片描述
在这里插入图片描述

namespace UF4{

    class UnionFind{

    private:
        int* rank;   // rank[i]表示以i为根的集合所表示的树的层数
        int* parent; // parent[i]表示第i个元素所指向的父节点
        int count;   // 数据个数

    public:
        // 构造函数
        UnionFind(int count){
            parent = new int[count];
            rank = new int[count];
            this->count = count;
            for( int i = 0 ; i < count ; i ++ ){
                parent[i] = i;
                rank[i] = 1;  // 初始时每个元素的层数为1
            }
        }

        // 析构函数
        ~UnionFind(){
            delete[] parent;
            delete[] rank;
        }

        // 查找过程, 查找元素p所对应的集合编号
        // O(h)复杂度, h为树的高度
        int find(int p){
            assert( p >= 0 && p < count );
            // 不断去查询自己的父亲节点, 直到到达根节点
            // 根节点的特点: parent[p] == p
            while( p != parent[p] )
                p = parent[p];
            return p;
        }

        // 查看元素p和元素q是否所属一个集合
        // O(h)复杂度, h为树的高度
        bool isConnected( int p , int q ){
            return find(p) == find(q);
        }

        // 合并元素p和元素q所属的集合
        // O(h)复杂度, h为树的高度
        void unionElements(int p, int q){

            int pRoot = find(p);
            int qRoot = find(q);

            if( pRoot == qRoot )
                return;

            // 根据两个元素所在树的元素个数不同判断合并方向
            // 将层数少的集合合并到层数多的集合上
            if( rank[pRoot] < rank[qRoot] ){
                parent[pRoot] = qRoot;
            }
            else if( rank[qRoot] < rank[pRoot]){
                parent[qRoot] = pRoot;
            }
            else{ // rank[pRoot] == rank[qRoot]
                parent[pRoot] = qRoot;
                rank[qRoot] += 1;   // 此时, 维护rank的值
            }
        }
    };
}

路径压缩 Path Compression(find)

路径压缩优化find的操作:之前的find是从一个节点开始,依次通过parent这个指针向上寻找它的根,这个过程把路径上的节点都遍历了一遍

在这里插入图片描述
在这里插入图片描述

只需要修改find

public:
		int find(int p){
        assert( p >= 0 && p < count );

        // path compression 1
        while( p != parent[p] ){
            parent[p] = parent[parent[p]];
            p = parent[p];
        }
        return p;
    }

路径压缩的最优情况:压缩到高度只有2,只要一步就找到根节点
(这种压缩方法理论上比上一个方法的压缩方法要更优,但是实际中并不一定是这样)
在这里插入图片描述

find的修改

int find(int p){
    assert( p >= 0 && p < count );

    // path compression 2, 递归算法
    if( p != parent[p] )
        parent[p] = find( parent[p] );
    return parent[p];
        }

经过路径压缩后并查集的操作,时间复杂度近乎是O(1)的(准确的说不是O(1))

namespace UF5{

    class UnionFind{

    private:
        // rank[i]表示以i为根的集合所表示的树的层数
        // 在后续的代码中, 我们并不会维护rank的语意, 也就是rank的值在路径压缩的过程中, 有可能不在是树的层数值
        // 这也是我们的rank不叫height或者depth的原因, 他只是作为比较的一个标准
        // 关于这个问题,可以参考问答区:http://coding.imooc.com/learn/questiondetail/7287.html
        int* rank;
        int* parent; // parent[i]表示第i个元素所指向的父节点
        int count;   // 数据个数

    public:
        // 构造函数
        UnionFind(int count){
            parent = new int[count];
            rank = new int[count];
            this->count = count;
            for( int i = 0 ; i < count ; i ++ ){
                parent[i] = i;
                rank[i] = 1;
            }
        }

        // 析构函数
        ~UnionFind(){
            delete[] parent;
            delete[] rank;
        }

        // 查找过程, 查找元素p所对应的集合编号
        // O(h)复杂度, h为树的高度
        int find(int p){
            assert( p >= 0 && p < count );

            // path compression 1
            while( p != parent[p] ){
                parent[p] = parent[parent[p]];
                p = parent[p];
            }
            return p;

            // path compression 2, 递归算法
//            if( p != parent[p] )
//                parent[p] = find( parent[p] );
//            return parent[p];
        }

        // 查看元素p和元素q是否所属一个集合
        // O(h)复杂度, h为树的高度
        bool isConnected( int p , int q ){
            return find(p) == find(q);
        }

        // 合并元素p和元素q所属的集合
        // O(h)复杂度, h为树的高度
        void unionElements(int p, int q){

            int pRoot = find(p);
            int qRoot = find(q);

            if( pRoot == qRoot )
                return;

            // 根据两个元素所在树的元素个数不同判断合并方向
            // 将元素个数少的集合合并到元素个数多的集合上
            if( rank[pRoot] < rank[qRoot] ){
                parent[pRoot] = qRoot;
            }
            else if( rank[qRoot] < rank[pRoot]){
                parent[qRoot] = pRoot;
            }
            else{ // rank[pRoot] == rank[qRoot]
                parent[pRoot] = qRoot;
                rank[qRoot] += 1;   // 此时, 维护rank的值
            }
        }
    };
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值