并查集的常规实现方法

在上一节中,我们讨论了并查集的简单实现方法,然而我们却发现并查集的效率好像并不是很高,因此,计算机科学家想出了另外一种方法来实现并查集。


在新的实现思路中,我们把每一个元素看做是一个节点,该节点只有一个指向其父亲的指针,也就是说parent[i]=j的意思就是i元素的父亲为j元素,因为 i 元素的父亲指针就是指向 j 节点的,如果一个元素没有父亲了,那么它的父亲指针就指向自己,这也是作为根节点的一个标志。



如上图所示,对于2元素,他的父亲指针是指向自己的,也就是说2元素是这个集合的根,我们可以把结合理解为一个家族,位于同一个人家族的人他们的祖宗肯定是同一个人的,类比成并查集的话,也就是所位于同一个集合中的元素其根节点都是同一个节点的,在上图中,最开始(5,6,7)为一个集合,(2,3)为一个集合,(1)为一个集合,

如果我们需要把1元素与3元素各自相应所在的集合合并的话,我们该怎么办呢?

我们可以类比成两个家族,如果两个不同的家族要合并成为一个家族的话,也就是两个家族的祖先必须是一样的,因此,我们只需要任意选一个家族的祖先,让该祖先的父节点由指向其自己改为指向另一个家族的祖先,这样两个家族的所有的成员的祖先就是同一个人元素了,我们也完成了两个家族的合并。

转换成并查集来说,就是把一个集合的根节点的父节点指向其另一个集合的根节点。这样就完成了Union操作。

对于上图来说,要把三个集合合并成为一个集合,我们只需要把5元素(根元素)的父亲指针指向2元素(根元素),把1元素(根元素)的父亲指针指向2元素(根元素)就好了。


如上图所示,这是一个通过上述方法已经完成了合并操作的并查集,通过partent[i]可以查到 i 元素的父亲节点,然后parent[父亲节点]又能够了解到父亲节点的父亲节点,一直到根元素位置。

另外:根元素的父亲指针指向其自己。

  

  让我们来看一下具体的代码实现:

 //找出元素p位于的集合的编号
        //即返回根元素
        int find(int p){
             assert(p>=0&&p<count);//防止数组越界
            while(p != parent[p]){//如果p元素的父亲指针指向的不是自己,说明p并不是集合中的根元素,还需要一直向上查找
                p=parent[p];//p变成其父亲
            }
            return p;//经过while循环后,p=parent[p],一定是一个根节点,我们返回即可
        }
        //判断两个元素是否位于同一个集合当中
        bool isconnected(int p,int q){
            return find(p)==find(q);//p,q元素的根节点一样,则位于同一个集合当中
        }
        //合并两个元素所在的集合
        void unoinelements(int p,int q){
            int proot=find(p);//找出p元素位于的集合的根元素
            int qroot=find(q);//找出q元素位于的结合的根元素
            if(proot==qroot){//如果两个元素的根元素都为同一个元素,则它们已经在同一个集合当中了
                return;
            }
            else{
                parent[proot]=qroot;//否则把一个根节点的父亲指针指向另一个根节点
            }
        }

接下来我们测试一下第二版本的并查集执行2万次操作的时间对比:


我们发现,UF2的效率要高出UF1,我们接下来测试20万次操作的时间对比:


貌似当数据量很大时,UF2并没有体现出很大的优化力度。

因此,我们需要对这一版本的并查集还要继续优化。

第二版本的完整源代码请点此此处访问。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值