【问题标题】:Why is my implementation of merge sort slow?为什么我的合并排序实现很慢?
【发布时间】:2021-12-04 11:43:11
【问题描述】:

我已经实现了合并排序,以便与我的作业算法一起使用。它可以工作,但是对于大量的测试用例,返回结果需要很长时间,而且不应该。我已经清除了我的主要方法,并且我确信合并排序是问题所在。有人能解释一下我做了什么让它如此低效吗?

    public static double[] merge(double[] arrayA, double[] arrayB){
        double[] arrayC = new double[0];

        while((arrayA.length != 0) && (arrayB.length != 0)){
            if(arrayA[0]>arrayB[0]){
                arrayC = Array.arrAdd(arrayC, arrayB[0]);
                arrayB = Array.arrRem(arrayB);
            }else{
                arrayC = Array.arrAdd(arrayC, arrayA[0]);
                arrayA = Array.arrRem(arrayA);
            }
        }
        while(arrayA.length != 0){
            arrayC = Array.arrAdd(arrayC, arrayA[0]);
            arrayA = Array.arrRem(arrayA);
        }
        while(arrayB.length != 0){
            arrayC = Array.arrAdd(arrayC, arrayB[0]);
            arrayB = Array.arrRem(arrayB);
        }

        return arrayC;
    }

    public static double[] mergeSort(double[] arrayA){
        if(arrayA.length == 1){
            return arrayA;
        }

        int a;
        int b;

        if(arrayA.length % 2 != 0){
            a = (arrayA.length+1)/2;
            b = (arrayA.length-1)/2;
        }else{
            a = (arrayA.length)/2;
            b = (arrayA.length)/2;
        }

            double[] array1 = new double[a];
            double[] array2 = new double[b];

        for(int i=0; i<array1.length; i++){
            array1[i] = arrayA[0];
            arrayA = Array.arrRem(arrayA);

        }
        for(int i=0; i<array2.length; i++){
            array2[i] = arrayA[0];
            arrayA = Array.arrRem(arrayA);
        }

        array1 = mergeSort(array1);
        array2 = mergeSort(array2);

        return merge(array1, array2);
    }

我还包括我的“arrAdd”和“arrRem”方法,它们用于在数组末尾添加一个元素并从索引 0 中删除一个。

    public static double[] arrAdd(double[] arrayA, double value){
        double[] arrayB = new double[arrayA.length+1];
        System.arraycopy(arrayA, 0, arrayB, 0, arrayA.length);
        arrayB[arrayA.length] = value;
        return arrayB;
    }

    public static double[] arrRem(double[] arrayA){
        double[] arrayB = new double[arrayA.length-1];
        System.arraycopy(arrayA, 1, arrayB, 0, arrayA.length - 1);
        return arrayB;
    }

【问题讨论】:

  • arrAddarrRem 方法创建新数组,将整个数组复制到其中,然后删除原始数组。归并排序的想法是移动单个值。不要创建/复制/删除整个数组。
  • 高效归并排序背后的想法是预分配用于merge 的缓冲区,只分配一次,并尽可能少地移动数据。参考可以看OpenJDK中的mergeSort implementation

标签: java performance sorting time-complexity mergesort


【解决方案1】:

创建数组的副本以添加/删除单个项目是一种操作数据的非常昂贵的方法。使用 ArrayList 或 'LinkedList' 而不是普通数组!

如果您需要一种更快的方式来处理原始类型,请查看 Colt 库:

https://dst.lbl.gov/ACSSoftware/colt/

【讨论】:

    【解决方案2】:

    因为arrayRem 效率极低。

    假设您有一个包含 100 万个元素的数组。你想通过这些元素。为此,您可以查看第一个元素,然后通过将所有其他元素复制到更靠近前面的位置来删除第一个元素,并重复此操作直到数组为空。

    因此,在使用第一个元素后,您将复制 999 999 个元素。
    使用第二个元素后,您将复制 999 998 个元素。
    使用第三个元素后,您将复制 999 997 个元素。
    (此处省略 999 996 行)
    使用第 1 000 000 个元素后,您将复制 0 个元素。

    也就是说,要读取长度为 100 万的数组,您将复制总共 1 000 000 * 500 000 = 500 000 000 000 个元素。这需要一段时间。

    合并排序的正确实现将通过递增索引来读取数组,而不是通过递增删除第一个元素并每次复制所有剩余元素。

    类似地,添加元素的有效方法是创建一个足够大的数组以预先容纳所有元素,然后通过跟踪下一个元素应写入的索引,将每个元素写入正确的位置,而不是为添加的每个元素复制所有现有元素。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-06
      • 2021-12-09
      • 2021-06-19
      • 2015-06-25
      • 2018-12-21
      • 2014-12-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多