【问题标题】:Merge Sort most efficient implementation合并排序最高效的实现
【发布时间】:2014-09-17 05:07:55
【问题描述】:

所以我想知道在 Java 中最有效的合并排序实现是什么(以防它在时间方面的效率会因语言而异)。这个问题可能微不足道,但我的最终目标是向更有经验的程序员学习。这是我做的两个例子:

//version I made.
public static double[] mergeSort(double[] arreglo) {
    if (arreglo.length > 1) {
        int d = (arreglo.length / 2);
        double[] arreglo1 = Arrays.copyOfRange(arreglo, 0, d),
                arreglo2 = Arrays.copyOfRange(arreglo, d, arreglo.length);
        arreglo1 = mergeSort(arreglo1);
        arreglo2 = mergeSort(arreglo2);
        return merge(arreglo1, arreglo2);
    } else {
        return arreglo;
    }
}

public static double[] merge(double[] arreglo1, double[] arreglo2) {
    double[] convi = new double[arreglo1.length + arreglo2.length];
    for (int i = 0, m1 = 0, m2 = 0; i < convi.length; i++) {
        if (arreglo1.length > m1 && arreglo2.length > m2) {
            if (arreglo1[m1] <= arreglo2[m2])
                convi[i] = arreglo1[m1++];
            else {
                convi[i] = arreglo2[m2++];
            }
        } else {
            convi[i] = (arreglo1.length == m1) ? arreglo2[m2++] : arreglo1[m1++];
        }
    }
    return convi;
}

//Taken out of Cormens book.
public static void mergeSort(int[] arreglo, int i, int f) {
    if (f > i) {
        int d = ((i + f) / 2);
        mergeSort(arreglo, i, d);
        mergeSort(arreglo, d + 1, f);
        merge(arreglo, i, d, f);
    }
}


public static void merge(int[] arreglo, int i, int m, int f) {
    int n1 = (m - i) + 1;
    int n2 = (f - m);
    int[] mitad1 = new int[n1 + 1];
    int[] mitad2 = new int[n2 + 1];
    for (int v = 0; v < n1; v++) {
        mitad1[v] = arreglo[i + v];
    }
    for (int p = 0; p < n2; p++) {
        mitad2[p] = arreglo[p + m + 1];
    }
    mitad1[n1] = Integer.MAX_VALUE;
    mitad2[n2] = Integer.MAX_VALUE;
    for (int r = i, m1 = 0, m2 = 0; r <= f; r++) {
        if (mitad1[m1] <= mitad2[m2]) {
            arreglo[r] = mitad1[m1];
            m1++;
        } else {
            arreglo[r] = mitad2[m2];
            m2++;
        }
    }
}

【问题讨论】:

  • 假设它们可以工作(而且它们看起来不错),这几乎肯定没关系。在大多数情况下,它们都将是O(n log n) 并且(通常)表现得比快速排序更差。一旦你看到更糟糕的情况,你会想知道为什么要花这么多时间来完善你的归并排序。
  • 是的,感谢您的回答。我主要是想练习。
  • 一个很好的通用技术是找到一个经常使用的库的源代码,它可以满足你的需求。 Java 库排序是一种稳定的归并排序,对几乎排序的列表进行了调整。许多聪明人付出了巨大的努力来减少许多架构上的运行时常数。一定要看看!

标签: java algorithm sorting mergesort


【解决方案1】:

以下程序翻译自Robert Sedgewick's Algorithms in C++, Parts 1-4中给出的C++示例

它引入了一种改进。它将整个排序数组的单个副本复制到一个辅助数组中,该辅助数组将被进一步处理。接下来,通过在辅助数组和原始数组之间交替进行辅助数组的递归拆分,这样就不会发生合并数组的额外复制操作。基本上,该算法在每个递归调用中切换输入和辅助数组的角色。例如,在概念上:

常规合并排序:

--合并

(((8) (5))((2) (3)))(((1) (7))((4) (6)))

(( 5 8 )( 2 3 ))(( 1 7 )( 4 6 ))

-- copy back and ignore previous (UNNECESSARY)

(( 5 8 )( 2 3 ))(( 1 7 )( 4 6 ))

– – – – – – – –

这个程序:

--合并

(((8) (5))((2) (3)))(((1) (7))((4) (6)))

(( 5 8 )( 2 3 ))(( 1 7 )( 4 6 ))

--向后合并

( 2 3 5 8 )( 1 4 6 7 )

(( 5 8 )( 2 3 ))(( 1 7 )( 4 6 ))    

此外,在将数组分成两半后得到足够小的数组,该算法使用insertion sort,因为它在小型数据集上的性能优于merge sort。可以通过反复试验确定何时准确使用insertion sort 的阈值。

代码:

static int M = 10;

//insertion sort to be used once the mergesort partitions become small enough
static void insertionsort(int[] a, int l, int r) {
    int i, j, temp;
    for (i = 1; i < r + 1; i++) {
        temp = a[i];
        j = i;
        while ((j > 0) && a[j - 1] > temp) 
        {
            a[j] = a[j - 1];
            j = j - 1;
        }
        a[j] = temp;
    }
}

//standard merging two sorted half arrays into single sorted array
static void merge(int[] merged_a, int start_a, int[] half_a1, int start_a1, int size_a1, 
                         int[] half_a2, int start_a2, int size_a2) {
    int i, j, k;
    int total_s = size_a1 + size_a2;
    for (i = start_a1, j = start_a2, k = start_a; k < (total_s); k++) {
        // if reached end of first half array, run through the loop 
        // filling in only from the second half array
        if (i == size_a1) {
            merged_a[k] = half_a2[j++];
            continue;
        }
        // if reached end of second half array, run through the loop 
        // filling in only from the first half array
        if (j == size_a2) {
            merged_a[k] = half_a1[i++];
            continue;
        }
        // merged array is filled with the smaller element of the two 
        // arrays, in order
        merged_a[k] = half_a1[i] < half_a2[j] ?
                      half_a1[i++] : half_a2[j++];
    }
}

//merge sort data during merging without the additional copying back to array
//all data movement is done during the course of the merges
static void mergesortNoCopy(int[] a, int[] b, int l, int r) {
    if (r - l <= M) {
        insertionsort(a + l, l, r - l);
        return;
    }
    int m = (l + r) / 2;
    //switch arrays to msort b thus recursively writing results to b
    mergesortNoCopy(b, a, l, m); //merge sort left
    mergesortNoCopy(b, a, m + 1, r); //merge sort right
    //merge partitions of b into a
    merge(a, l, b, l, m - l + 1, b, m + 1, r - m); //merge
}

static void mergesort(int[] a) {
    int[] aux = Arrays.copyOf(a, a.length);
    mergesortNoCopy(a, aux, 0, a.length - 1);
}

其他一些可能的改进:

如果已经排序则停止。

检查前半部分的最大项≤后半部分的最小项。 对部分有序数组有帮助。

// after split, before merge
if (a[mid] <= a[mid + 1]) return;

编辑: 这是我在不同版本的 Mergesort 及其改进中发现的 good document

【讨论】:

  • 谢谢,我明白了在数组和辅助之间交替的概念,这样我就不必在每次递归调用中复制,但我不明白你为什么要添加数组类型和一个 int。这里:合并(a + l,b + l,m - l + 1,b + m + 1,r - m)
  • @MelvinAcuñaGuntanis 对不起,从 C++ 翻译 :)。这样做是在函数 bi a[l] 中有 a[0],b 也一样,所以你也应该传递起点
  • 这种没有复制的合并排序看起来与 CLRS 中描述的就地合并排序相同。
【解决方案2】:

第二个效率更高,因为它避免了所有复制步骤。它也简单一百万倍,更明显,有它自己的价值,

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-11
    • 1970-01-01
    • 1970-01-01
    • 2018-12-01
    • 1970-01-01
    • 2014-07-01
    • 1970-01-01
    相关资源
    最近更新 更多