快速排序比合并排序慢?

2022-09-02 03:51:27

我昨天正在致力于实现一个快速排序,然后我运行它,期望比Mergesort(我也实现了)更快的运行时。我运行了这两个,虽然快速排序对于较小的数据集<100个元素更快(我确实验证了它的工作原理),但合并排序很快成为更快的算法。我被教导说,快速排序几乎总是比mergesort“更快”,我知道在这个话题上有一些争论,但我至少期望它比这更接近。对于>10000个元素的数据集,合并排序的速度提高了4倍以上。这是预料之中的,还是我的快速排序代码中存在错误?

合并排序:

public static void mergeSort(int[ ] e)
{
    if (e.length <= 1) return;
    int[] first = new int[e.length/2];
    int[] second = new int[e.length - first.length];
    System.arraycopy(e, 0, first, 0, first.length);
    System.arraycopy(e, first.length, second, 0, second.length);
    mergeSort(first);
    mergeSort(second);
    System.arraycopy(merge(first, second), 0, e, 0, e.length);
}

private static int[] merge(int[] first, int[] second) {
    int iFirst = 0;
    int iSecond = 0;
    int iCombined = 0;

    int[] combined = new int[first.length + second.length];
    while(iFirst < first.length && iSecond < second.length) {
        if (first[iFirst] > second[iSecond]) {
            combined[iCombined++] = second[iSecond++];
        }
        else combined[iCombined++] = first[iFirst++];
    }
    for(; iFirst < first.length; iFirst++) {
        combined[iCombined++] = first[iFirst];
    }
    for(; iSecond < second.length; iSecond++) {
        combined[iCombined++] = second[iSecond];
    }
    return combined;
}

快速排序:

public static void quicksort(int[] a, int first, int last) {
    if (first >= last) return;

    int partitionIndex = partition(a, first, last);
    quicksort(a, first, partitionIndex - 1);
    quicksort(a, partitionIndex + 1, last);
}

public static int partition(int[] x, int first, int last) {
    int left = first;
    int right = last;
    int pivot = x[first];
    int pivotIdx = first;

    while(left <= right) {
        while(left < x.length && x[left] <= pivot) left++;
        while(right >= 0 && x[right] > pivot) right--;
        if (left <= right) {
            int temp = x[left];
            x[left] = x[right];
            x[right] = temp;
        }
    }
    pivotIdx = right;
    x[first] = x[right];
    x[pivotIdx] = pivot;
    return pivotIdx;
}

答案 1

实际上,我刚刚在C语言中写了一个“链表比较排序演示程序”,并得出了类似的结论(对于大多数用途,mergesort将击败quicksort),尽管我被告知quicksort通常不用于链接列表。我要指出的是,枢轴值的选择是一个怪物因素 - 我最初的版本使用随机节点作为枢轴,当我稍微改进它以两个(随机)节点的平均值时,1000000条记录的计算时间从超过4分钟增加到不到10秒,使其与mergesort相提并论。

Mergesort和quicksort具有相同的大O最佳情况(n * log(n)),尽管人们可能会试图声称,大O实际上是关于迭代计数而不是比较计数。它们之间可以产生的最大区别将始终是对快速排序的损害,并且它涉及已经在很大程度上排序或包含大量联系的列表(当快速排序比mergesort更好时,差异不会那么大)。这是因为关系或已排序的段通过合并排序直接简化;当两个拆分列表返回进行合并时,如果一个列表已经包含所有较小的值,则左侧的所有值将一次一个地与右侧的第一个元素进行比较,然后(由于返回的列表具有内部顺序)不需要进行进一步的比较,并且右侧只是迭代到末尾。也就是说,迭代次数将保持不变,但比较次数将减少一半。如果您正在谈论实际时间并对字符串进行排序,那么比较是昂贵的。

如果枢轴值没有仔细确定,快速排序中的联系和已排序的段很容易导致列表不平衡,而不平衡的列表(例如,右侧的一个,左侧的十个)是导致速度变慢的原因。因此,如果您可以让快速排序在已排序的列表中执行得与在 ramdomized 列表中一样好,那么您就有了一种查找透视表的好方法。

如果您有兴趣,演示程序会生成如下输出:

[root~/C] ./a.out -1 3 
Using "", 0 records
Primary Criteria offset=128

Command (h for help, Q to quit): N
How many records? 4000000
New list is 562500.00 kb

Command (h for help, Q to quit): m

Mergesorting..............3999999 function calls
123539969 Iterations     Comparison calls: 82696100
Elapsed time: 0 min 9 sec


Command (h for help, Q to quit): S
Shuffled.

Command (h for help, Q to quit): q

Quicksorting..............4000000 function calls
190179315 Iterations     Comparison calls: 100817020
Elapsed time: 0 min 23 sec

没有疯狂的kolors。关于这个页面的一半,我还有更多关于它的东西。

这两种排序都不需要对链表进行额外的内存。


答案 2

对于基于随机数组的数据,Mergesort要慢得多,只要它适合RAM。这是我第一次看到它被辩论。

  • qsort 最短的子数组。
  • 切换到 5-25 个元素下方的插入排序
  • 执行正常的透视表选择

您的 qsort 非常慢,因为它尝试对长度为 2 和 3 的 qsort 数组进行分区和 qsort。