美文网首页算法与数据结构随笔-生活工作点滴
算法与数据结构系列之[并查集-下]

算法与数据结构系列之[并查集-下]

作者: 秦老厮 | 来源:发表于2019-07-10 10:41 被阅读4次

接着上篇介绍并查集的优化方法

3.路径压缩

上一篇介绍了基于rank的优化,但是依然有一定的问题需要解决,因为当我们的数据量越来越大时,树的高度可能也会随之变大,数据量小的时候影响不大,但当数据量很大时,树的高度比较高,查询数据就会变慢了,所以我们有必要通过减小树的高度而不破坏原先的连接关系的方法来解决以上问题。我们可以在每次查询时将树的高度缩减,这就是路径压缩。

图一

如上图,三个图表示的结果是相同的,每一棵树的任意两个节点都是连接的,但是树的高度却不同,2图比较理想的压缩结果,所有的节点都直接连在一个根节点上,树的高度始终为2,但是比较难以实现。所以退而求其次,将1图的树压缩成3图的树,高度以减小了。

压缩过程:每次执行查询方法时,都执行以下parent[i] = parent[parent[i]],也就是每次查询时先判断要查询的节点的父节点是否是根节点,不是的话让当前节点指向当前节点的父节点的父节点,完成一次压缩,如果要查询的节点的父节点的父节点还不是根节点,就继续向上遍历,直到要查询的节点的父节点的父节点是根节点。

图二

代码实现:

public class UnionFind5 implements UF {
   int[] parent;
   int[] rank; //rank[i] 表示以i为根节点的集合所表示的树的层数
   public UnionFind5(int size){
       parent = new int[size];
       rank = new int[size];
       for (int i = 0; i < size; i++) {
           parent[i] = i;
           rank[i] = 1;
       }
   }

   @Override
   public int getSize() {
       return parent.length;
   }

   //查找i对应的集合编号
   //时间复杂度为O(h),h为树的高度
   private int find(int i){
       if(i < 0 || i>= parent.length)
           throw new IllegalArgumentException("非法索引");
       while (i != parent[i]) {
           parent[i] = parent[parent[i]];   //每次i循环向上寻找根节点的过程中,都将i重新指向它的根节点的根节点,从而使树的深度变浅
           i = parent[i];
       }
       return i;
   }

   @Override
   public boolean isConnected(int p, int q) {
       return find(p) == find(q);
   }

   //合并操作
   //时间复杂度为O(h),h为树的高度
   @Override
   public void unionElements(int p, int q) {
       int pRoot = find(p);
       int qRoot = find(q);
       if(pRoot == qRoot)
           return;
       //根据两个元素所在的树的rank不同判断合并方向
       //将元rank低的集合合并到rank高的集合
       if(rank[pRoot] < rank[qRoot]){
           parent[pRoot] = qRoot;

       }
       else if (rank[qRoot] < rank[pRoot]){
           parent[qRoot] =pRoot;
       }
       else{  //rank[pRoot] == rank[qRoot]   以pRoot和qRoot为根节点的两棵树层数一样时,合并时可以将任意一个合并到另一个即可
           parent[pRoot] = qRoot;
           rank[qRoot] += 1;  //合并完成后层数加1
       }
   }
}

性能测试:

测试代码

public class Test {
    private static double testUF(UF uf,int m){
        int size = uf.getSize();
        Random random = new Random();

        long startTime = System.nanoTime();

        for (int i = 0; i < m; i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.unionElements(a,b);
        }

        for (int i = 0; i < m; i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.isConnected(a,b);
        }

        long endTime = System.nanoTime();

        return (endTime - startTime) / 1000000000.0;
    }

    public static void main(String[] args) {
        int size = 10000000;
        int m = 10000000;
        UnionFind1 unionFind1 = new UnionFind1(size);
       /* System.out.println("UnionFind1: " + testUF(unionFind1,m) + " s");
        UnionFind2 unionFind2 = new UnionFind2(size);
        System.out.println("UnionFind2: " + testUF(unionFind2,m) + " s");*/
        UnionFind3 unionFind3 = new UnionFind3(size);
        System.out.println("UnionFind3: " + testUF(unionFind3,m) + " s");
        UnionFind4 unionFind4 = new UnionFind4(size);
        System.out.println("UnionFind4: " + testUF(unionFind4,m) + " s");
        UnionFind5 unionFind5 = new UnionFind5(size);
        System.out.println("UnionFind5: " + testUF(unionFind5,m) + " s");
        
       /* UnionFind6 unionFind6 = new UnionFind6(size);
        System.out.println("UnionFind6: " + testUF(unionFind6,m) + " s");*/
    }
}

执行结果


图三

路径压缩后的性能有了显著的提升。

上一篇中维护了rank来做合并时的比较,上篇当时说的是在没有介绍路径压缩之前可以把rank理解为树的高度或深度,这篇介绍了路径压缩后这个概念就变了,因为我们在压缩树的过程中,树的高度或深度变小了,而我们这里并没有修改rank,所以rank就不在代码树的高度或深度,而是作为一个排序,在合并时依然可以比较rank大小进行合并,将rank小的树指向rank大的树,rank的功能没有变,依然可以胜任这个合并时的比较工作。

4.理想化的路径压缩

在上面介绍了理想化的路径压缩比价难实现,但是并不是不可以实现,我们完全可以借助递归方法把图一中的第一棵树压缩成图一中的第二棵树的形状,只是这种理想化的压缩对性能提升不了多小,甚至可能使性能稍微变差,因为递归调用是要耗费性能的。

代码实现:

public class UnionFind6 implements UF {
   int[] parent;
   int[] rank; //rank[i] 表示以i为根节点的集合所表示的树的层数
   public UnionFind6(int size){
       parent = new int[size];
       rank = new int[size];
       for (int i = 0; i < size; i++) {
           parent[i] = i;
           rank[i] = 1;
       }
   }

   @Override
   public int getSize() {
       return parent.length;
   }

   //查找i对应的集合编号
   //时间复杂度为O(h),h为树的高度
   private int find(int i){
       if(i < 0 || i>= parent.length)
           throw new IllegalArgumentException("非法索引");
      if (i != parent[i]) {
           parent[i] = find(parent[i]);
       }
       return parent[i];
   }

   @Override
   public boolean isConnected(int p, int q) {
       return find(p) == find(q);
   }

   //合并操作
   //时间复杂度为O(h),h为树的高度
   @Override
   public void unionElements(int p, int q) {
       int pRoot = find(p);
       int qRoot = find(q);
       if(pRoot == qRoot)
           return;
       //根据两个元素所在的树的rank不同判断合并方向
       //将元rank低的集合合并到rank高的集合
       if(rank[pRoot] < rank[qRoot]){
           parent[pRoot] = qRoot;

       }
       else if (rank[qRoot] < rank[pRoot]){
           parent[qRoot] =pRoot;
       }
       else{  //rank[pRoot] == rank[qRoot]   以pRoot和qRoot为根节点的两棵树层数一样时,合并时可以将任意一个合并到另一个即可
           parent[pRoot] = qRoot;
           rank[qRoot] += 1;  //合并完成后层数加1
       }
   }
}

性能测试:
代码同上,只加两句

UnionFind6 unionFind6 = new UnionFind6(size);
System.out.println("UnionFind6: " + testUF(unionFind6,m) + " s");

执行结果


图四 本人微信公众号,点关注,不迷路

相关文章

  • 算法与数据结构系列之[并查集-下]

    接着上篇介绍并查集的优化方法 3.路径压缩 上一篇介绍了基于rank的优化,但是依然有一定的问题需要解决,因为当我...

  • 并查集练习

    并查集 参考《算法笔记》 三要素:并,查,集。 并差集与一个算法有关,kruskal算法,就可通过并查集实现。 1...

  • 最小生成树算法——Kruskal算法

    算法思想 先了解下什么叫并查集 并查集 (Union Find Set)又叫不相交集数据结构(Disjointed...

  • 算法与数据结构系列之[并查集-上]

    1.概述 并查集是一种树形的数据结构,但是这种树很特殊,每棵树都是从子节点指向父节点的,在使用中也常常以森林来表示...

  • 算法与数据结构系列之[并查集-中]

    上篇介绍了并查集的基本实现,这篇介绍几种并查集的优化方法。 1.基于size优化: 上一篇当中树实现并查集的方法中...

  • 数据结构与算法之 并查集

    1. 什么是并查集?并查集解决哪类问题? 并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构...

  • 数据结构与算法之并查集

    需求分析 假设有n个村庄,有些村庄之间有连接的路,有些村庄之间并没有连接的路 如上图:我们很容易发现1,2,3,4...

  • 从并查集Union Find算法谈算法解决实际问题的过程

    从并查集Union Find算法谈算法解决实际问题的过程算法并查集算法寻找路径 从并查集Union Find算法谈...

  • 深入理解并查集

    并查集是一种树形结构,它是由并查集算法进行维护的。而并查集算法(Union-find-algorithm),顾名思...

  • 1-3 课程的注意事项

    1.《玩转数据结构》和《算法与数据结构》的区别  后者的主要内容包括三个数据结构(二分搜索树、堆、并查集)、排序算...

网友评论

    本文标题:算法与数据结构系列之[并查集-下]

    本文链接:https://www.haomeiwen.com/subject/txyckctx.html