基于Rank的并查集优化

在上一小节中,我们讨论了基于Size的并查集优化方法,即在合并两个集合时,通过判断两个集合元素的数量大小来决定把哪一个集合并入另一个集合当中,从而减少了因为合并集合使得合并后树的层数增多的情况,因此执行find操作所需的步骤数量也大大减少了。但是,没有绝对完美的优化方法,这种基于Size的合并策略在有的时候却并不能很好的解决合并时发生层数增多的问题,例如下面这种情况:



我们可以看到,如果我们现在需要把4和2两个元素合并在一起,即把他们各自的集合合并为同一个集合,2的根元素为7,4的根元素为8,且7为根的集合的树所含有的元素个数为6个,而以8为根的集合所含有的元素个数为3个,根据Size的策略,我们会把8节点的父亲指针指向7根节点来合并这两个集合。如下图所示:

然而,经过这样归并后,这棵树的层数就由原来的两层变成了四层,如果我们换一个方向去合并这两个集合,把7根节点的父亲指针指向8根节点的话来合并这两个集合,树形就如下图所示:

我们发现,原来以8作为根节点的树的层数为3层,现在合并后的集合的层数还是3层,这样合并后的集合的层数也要比上面的4层少一层,因此我们可以得出这样的一个结论:仅仅依靠集合的Size来判断由谁指向谁,并不是完全准确的,更准确的是比较集合的层数来判断谁指向谁,这样最后合并出来的集合的层数能够竟可能的压缩至最小,因此执行find操作的效率将大大提高。在集合中,层数越少,对于每一个节点平均来说,找到根节点所需要查找的次数就会越小。

因此,我们可以用一个rank数组来替换原来的Size数组,Rank[i]表示以 i 为根节点的集合的层数,即树的高度。下面我们来看一下具体的实现代码:

并查集的基础结构:

 private:
        int* parent;//parent指针指向一个专门用来记录元素父亲元素的指针
        int count;//记录集合中元素的数量
        int *rank;//rank[i]表示以i为根的集合的层数
构造函数:
 UnionFind4(int n){
            parent=new int[n];//初始化parent数组
            rank=new int[n];//rank[i]表示以i为根元素的集合所表示的树的层数
            count=n;
            /*
             * 切记一定要记得初始化parent数组
             * 使每个元素的parent指针都指向自己
             */
            for(int i=0;i<n;i++){
                parent[i]=i;
                rank[i]=1;//最开始所有的元素都指向自己,每一个元素都是根元素,每一个集合都只有一层
            }

        }
接下来是最重要的连接操作:

对于两个集合来说,如果两个集合的层数不一样,我们只需要把层数小的集合的根元素的父亲节点指向另一个集合的根元素就好了,而且最后合并出来的集合的层数是不变的。还是以下图为例:


根据rank的策略,根节点7元素的父亲节点指向了8元素,然而合并后的集合的层数原来为3层(8,3,4集合),7节点的集合与其合并后,合并后的集合的层数仍然为3层,是不会变的。


合并后集合层数唯一会变的情况,就是两个集合的层数一模一样时。我们可以这样去理解,假设两个集合都只有一个元素,那么这两个集合的层数都为一层,层数相同时,此时谁的根节点的父亲节点指向另一个根节点都无所谓了,但是这样合并后的集合层数要比原来多了一层。(原来两个集合都为一层,合并后的集合就变成两层了),因此,Unionelements的代码具体如下:

  //合并两个元素所在的集合
        void unoinelements(int p,int q){
            int proot=find(p);//找出p元素位于的集合的根元素
            int qroot=find(q);//找出q元素位于的结合的根元素
            if(proot==qroot){//如果两个元素的根元素都为同一个元素,则它们已经在同一个集合当中了
                return;
            }
            else{//两个元素在不同的集合当中
                if(rank[proot]<rank[qroot]) {//p元素所在的集合的层数小于q元素所在的集合的层数
                    parent[proot] = qroot;//p集合的根节点父亲指针指向q集合的根节点
                }
                else if(rank[proot]>rank[qroot]){//此时p集合的层数要大于q集合的层数
                    parent[qroot]=proot;//q集合的根元素的父亲节点指向p集合的根元素
                }
                else{//p集合与q集合的层数相同
                    parent[proot]=qroot;//二者根元素任意连接都可,这里默认把p集合的根元素父亲节点指向q集合的根节点
                    rank[qroot]++;//q集合的层数此时会增加一层
                }
            }
        }

我们接下来在检验一下基于Rank优化的并查集的效率如何,同样是进行200万次的操作:

int main() {
        int n=1000000;
        UnionFindTestHelper::TestUF1(n);
        UnionFindTestHelper::TestUF2(n);
        UnionFindTestHelper::TestUF3(n);
        UnionFindTestHelper::TestUF4(n);
    return 0;
}
结果如下:



我们发现,基于Rank的优化策略效率同样很高,但是有的时候可能会比基于Size优化策略的并查集慢一些,这是因为基于Rank优化的并查集的Unionelements操作中的if else判断语句更多了,因此要比Size慢一点,但是效率仍然很高,还可以克服一些极端的情况,因此实现并查集使用rank策略就好了。

如需获取本次版本的所有源代码,请点击此处移步我的Github代码仓库。




评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值