Merge排序的Java实现不起作用

时间:2013-09-07 21:35:58

标签: java algorithm sorting mergesort

我正在尝试实现此版本的合并排序,如Cormen的算法简介中所示。

public static void merge(int[] A, int p, int q, int r) {
    int lengthOfLeftSubarray = q - p + 1;
    int lengthOfRightSubarray = r - q;

    int[] L = new int[lengthOfLeftSubarray + 1];
    int[] R = new int[lengthOfRightSubarray + 1];

    for(int i=0; i<lengthOfLeftSubarray; i++) 
        L[i] = A[p + i];

    for(int i=0; i<lengthOfRightSubarray; i++) 
        R[i] = A[q + i];

    L[lengthOfLeftSubarray] = -1;
    R[lengthOfRightSubarray] = -1;

    int i = 0, j = 0;
    for(int k=p; k<r; k++) {
        if(L[i] <= R[j]) {****
            A[k] = L[i];
            i++;
        }
        else {
            A[k] = R[j];
            j++;
        }
    }
}

public static void mergesort(int[] A, int p, int r) {
    if(p < r){
        int q = (p + r) / 2;
        mergesort(A, p, q);
        mergesort(A, q + 1, r);
        merge(A, p, q, r);
    }       
}

public static void main(String[] args) {
    int[] unsorted = {12, 16, 4, 2, 7, 6};
    Sorting.mergesort(unsorted, 0, unsorted.length - 1);
    System.out.println(Arrays.toString(unsorted));
}

我有两个问题:

  1. 在书中,提到了一张哨兵卡,它应该是放入数组的某种特殊值,不会干扰排序。我已经使用了-1,因为我无法想到使用无限的方法,正如书中所建议的那样。谁能解释一下哨兵是什么?
  2. 代码在merge方法中抛出一个ArrayOutOfBounds异常,其中四颗星是( * *)。关于为什么会发生这种情况的任何想法?

1 个答案:

答案 0 :(得分:0)

粗略地看一眼(即我没有修复代码并在本地运行以确认测试用例有效),看起来有两个错误:

1)

for(int i=0; i<lengthOfRightSubarray; i++) 
    R[i] = A[q + i];

正确的子阵列应该从q + 1开始,就像你进行递归调用一样:

mergesort(A, q + 1, r);

2)在OP评论中看起来你已经对它进行了排序,但你选择的标记值不起作用。 sentinel值的点取决于算法。在这种情况下,sentinel值的点是它应该大于输入数组unsorted中的任何元素。特别是,当您执行merge时,整个点就是您有两个已排序的子数组,并希望将它们合并为一个更大的且仍然排序的数组。如此快速的例子:

---- At the beginning ----
LEFT: {4, 12 16, INFINITY}
       ^
RIGHT: {2, 6, 7, INFINITY}
        ^
MERGED ARRAY: { }
               ^

---- After one iteration of the for loop ----
LEFT: {4, 12 16, INFINITY}
       ^
RIGHT: {2, 6, 7, INFINITY}
           ^
MERGED ARRAY: {2}
               ^

---- After second iteration of the for loop ----
LEFT: {4, 12 16, INFINITY}
          ^
RIGHT: {2, 6, 7, INFINITY}
           ^
MERGED ARRAY: {2, 4}
                  ^

---- After third iteration of the for loop ----
LEFT: {4, 12 16, INFINITY}
          ^
RIGHT: {2, 6, 7, INFINITY}
              ^
MERGED ARRAY: {2, 4, 6}
                     ^

---- After fourth iteration of the for loop ----
LEFT: {4, 12 16, INFINITY}
          ^
RIGHT: {2, 6, 7, INFINITY}
                 ^
MERGED ARRAY: {2, 4, 6, 7}
                        ^

你可以在这里看到为什么哨兵价值很重要。由于右侧索引指向INFINITY,因此左侧子阵列中的元素将被适当地合并。将-1替换为INFINITY并轻松查看merge触发ArrayOutOfBounds例外很容易。

sentinel值有用的全部原因仅仅是因为当一个子数组耗尽值时我们不想处理这种情况。您可以轻松地实现该算法并检查子阵列何时耗尽元素(此时您只需将其与其他子阵列的其余部分填充),但它的清洁性稍差且难以推理。设置一个sentinel值可以确保子数组都不会耗尽允许您以更简单的方式编写证明(算法工作!)的元素。