为什么在MergeSort中使用InsertionSort而不是Merge平均更快?

时间:2014-01-02 15:49:48

标签: c# algorithm

最近,我通过简单地在小子列表中使用 InsertionSort 来着迷于 ShellSort 算法的想法,然后最终使用InsertionSort作为整个列表。

所以,我想为什么不将 MergeSort InsertionSort 结合使用(而不是使用Merge()函数,让我们使用 InsertionSort )。由于InsertionSort擅长排序部分排序列表,而 MergeSort 的想法是将两个排序列表合并为一个排序列表。

然后,我使用merge()函数测试MergeSort,使用带有随机值10,000,000个元素的ArrayionSort()测试MergeSort。事实证明,带有InsertionSort()的MergeSort比带有merge()函数的MergeSort执行快几倍。由于提出准确的数学证明超出了我的能力,我来这里是为了寻找合乎逻辑的理由。以下是我要确认的内容:

  • 对于大型阵列,带有merge()函数的MergeSort是否会平均优于带有InsertionSort()的MergeSort,反之亦然?
  • 也许我的MergeSort with merge()函数效率低下。
  • 在MergeSort中使用ShellSort而不是InsertionSort会产生更快的性能吗?
  • 因为,使用InsertionSort的MergeSort并不是一个坏主意,我相信已经有人发现了它。我想知道它是否有任何独特的算法名称。

以下是MergeSort()

的实现
public static void MergeSort(int[] array)
{
    int[] aux = new int[array.Length];
    MergeSort(array, aux, 0, array.Length - 1);
}

public static void MergeSort(int[] array, int[] aux, int low, int high) 
{
    if (low >= high) return;

    int mid = (low + high) / 2;

    MergeSort(array, aux, low, mid);
    MergeSort(array, aux, mid + 1, high);

    Merge(array, aux, low, mid, high);
}

protected static void Merge(int[] array, int[] aux, int low, int mid, int high) {
    // copy into aux array
    for (int i = low; i <= high; i++) aux[i] = array[i];

    // merge
    int j = low, k = mid + 1;
    for (int o = low; o <= high; o++) {
        if (j > mid)
            array[o] = aux[k++];
        else if (k > high)
            array[o] = aux[j++];
        else if (aux[k] < aux[j])
            array[o] = aux[k++];
        else
            array[o] = aux[j++];
    }
}

以下是带有InsertionSort()

的MergeSort
public static void MergeInsertionSort(int[] array) 
{
    MergeInsertionSort(array, 0, array.Length - 1);
}

public static void MergeInsertionSort(int[] array, int low, int high) 
{
    if (low >= high) return;
    if (low + 1 == high) {
        // sort two elements
        if (array[low] > array[high]) {
            int tmp = array[low];
            array[low] = array[high];
            array[high] = tmp;
        }
    } else {
        int mid = (low + high) / 2;

        MergeInsertionSort(array, low, mid);
        MergeInsertionSort(array, mid + 1, high);

        // do insertion sort
        for (int i = mid + 1, j; i <= high; i++) {
            int ins = array[low];

            // move the element into correct position
            for (j = i - 1; (j >= low) && (ins < array[j]); j--) {
                array[j + 1] = array[j];
            }

            array[j + 1] = ins;
        }
    }
}

以下是可运行的代码,您可以在计算机上测试它: http://pastebin.com/4nh7L3H9

2 个答案:

答案 0 :(得分:1)

你根本没有测试同样的东西。您的Merge方法使用辅助数组,它首先要做的是在执行实际的合并工作之前将初始数组复制到aux数组。因此,每次调用Merge时,您最终会完成两倍的工作。

您可以通过arrayaux进行智能交换来消除额外的副本。这在非递归实现中更容易处理,但是递归版本可以实现。我将此作为练习。

您的MergeInsertionSort方法的运作方式大不相同。它根本没有进行合并。它只是拆分数组并在越来越大的范围内进行插入排序。

我们的想法是使用插入排序,以便在范围较小时减少合并的开销。通常它看起来像这样:

public static void MergeSort(int[] array, int[] aux, int low, int high) 
{
    if (low >= high) return;

    if ((high - low) < MergeThreshold)
    {
        // do insertion sort of the range here
    }
    else
    {
        int mid = (low + high) / 2;

        MergeSort(array, aux, low, mid);
        MergeSort(array, aux, mid + 1, high);

        Merge(array, aux, low, mid, high);
    }
}

然后将MergeThreshold设置为&#34;小范围&#34;您确定的价值是合适的。通常情况下,这个范围在5到20之间,但您可能希望尝试不同的值和不同的类型(整数,字符串,复杂对象等)以获得良好的全面数字。

答案 1 :(得分:0)

这里的问题是您的插入排序已损坏,使其运行速度快得多,但返回了无意义的输出(所有元素在外观上均具有相同的编号)。这是运行tmp之后的MergeInsertionSort数组的示例(数组大小更改为10):

1219739925
1219739925
1219739925
1219739925
1219739925
1219739925
1219739925
1219739925
1219739925
1275593566

这是您的代码:

// do insertion sort
for (int i = mid + 1, j; i <= high; i++) {
    int ins = array[low];

    // move the element into correct position
    for (j = i - 1; (j >= low) && (ins < array[j]); j--) {
        array[j + 1] = array[j];
    }

    array[j + 1] = ins;
}

问题是这条线

    int ins = array[low];

这应该是:

    int ins = array[i];

解决此问题后,您会发现MergeSortMergeInsertionSort更有效率(您必须减小数组大小,以便在合理的时间内运行)。


另一方面,这花了我一段时间,因为我最初只是检查输出是否已排序(是,但不是输入的排序版本),而不是实际检查它是否是输入的排序版本。直到我查看发现问题的输出。