【问题标题】:Why is this implementation of Quick Sort slower than qsort?为什么快速排序的这种实现比 qsort 慢?
【发布时间】:2016-02-17 00:58:15
【问题描述】:

对算法进行一些刷新。我写了这个快速排序实现:

#define SWAP(X, Y, Type) { Type Temp = (X); (X) = (Y); (Y) = Temp; }

int QSPartition(int* Array, int StartIndex, int EndIndex)
{
    int PivotDestinationIndex = StartIndex;
    int Pivot = Array[EndIndex];
    for(int i = StartIndex; i <= EndIndex; i++)
    {
        if (Array[i] < Pivot)
        {
            SWAP(Array[i], Array[PivotDestinationIndex], int);
            PivotDestinationIndex++;
        }
    }
    SWAP(Array[PivotDestinationIndex], Array[EndIndex], int);
    return PivotDestinationIndex;
}

void QuickSort(int* Array, int StartIndex, int EndIndex)
{
    if (StartIndex >= EndIndex)
        return;

    int PivotIndex = QSPartition(Array, StartIndex, EndIndex);
    QuickSort(Array, PivotIndex + 1, EndIndex);
    QuickSort(Array, StartIndex, PivotIndex - 1);
}

在我的主程序中:

#define Measure(Iterations, What, Code)\
{\
    clock_t Begin = clock();\
    for(int _im_ = 0; _im_ < Iterations; _im_++)\
    {\
        Code;\
    }\
    clock_t End = clock();\
    printf("Time took to " What ": %.1f ms\n", (double)(End - Begin));\
}

int CompareInt(const void* x, const void* y) { return *(int*)x - *(int*)y; }

int main()
{
    srand(time(0));

    int Count = 100000;
    int* Array0 = (int*)malloc(sizeof(int) * Count);
    int* Array1 = (int*)malloc(sizeof(int) * Count);

    for (int i = 0; i < Count; i++)
    {
        int RandomValue = rand() % 100;
        Array0[i] = RandomValue;
        Array1[i] = RandomValue;
    }

    Measure(1, "My Quick", QuickSort(Array0, 0, Count - 1));
    Measure(1, "C  Quick", qsort(Array1, Count, sizeof(int), CompareInt));

    getchar();
    return 0;
}

输出是“My Quick”总是在 125 ms 附近,而“C Quick”总是在 25 ms 附近。 (32 位 DEBUG 构建 - 在 RELEASE 上仍然慢 5 倍)

发生了什么事?为什么我的实施速度较慢? (我尝试内联函数,但效果不大)还有其他问题吗? (我计时的方式,或者我用随机值填充数组的方式,或者......?)

【问题讨论】:

  • qsort 是一种快速排序。它不一定是快速排序的实现。
  • 我没有得到反对票...
  • 可能会有一些优化来使用一些手动编写的代码没有利用的 cpu 指令
  • 在快速排序不太理想的情况下,许多 qsort 会切换到非快速排序算法。如果内存够用,当您的元素少于 16 个时,MSVC 将切换到堆排序。
  • 我没有运行任何测试,但一个很大的不同是qsort 通常在大小较小时通过下拉到另一种排序来进行优化。您可以在 glibc qsort source: 插入排序中看到它,当有 4 个或更少元素时。它也是迭代的,而不是递归的。

标签: c performance sorting quicksort


【解决方案1】:

尝试其中一种,您需要更改 std::swap 以换成 C。

使用中间值作为枢轴的快速排序:

void QuickSort(uint32_t a[], int lo, int hi) {
    int i = lo, j = hi;
    uint32_t pivot = a[(lo + hi) / 2];
    while (i <= j) {            // partition
        while (a[i] < pivot)
            i++;
        while (a[j] > pivot)
            j--;
        if (i <= j) {
            std::swap(a[i], a[j]);
            i++;
            j--;
        }
    }
    if (lo < j)                 // recurse
        QuickSort(a, lo, j);
    if (i < hi)
        QuickSort(a, i, hi);
}

使用低、中、高的中值作为枢轴的快速排序

void QuickSort(uint32_t a[], int lo, int hi) {
    int i = lo, j = (lo + hi)/2, k = hi;
    uint32_t pivot;
    if (a[k] < a[i])            // median of 3
        std::swap(a[k], a[i]);
    if (a[j] < a[i])
        std::swap(a[j], a[i]);
    if (a[k] < a[j])
        std::swap(a[k], a[j]);
    pivot = a[j];
    while (i <= k) {            // partition
        while (a[i] < pivot)
            i++;
        while (a[k] > pivot)
            k--;
        if (i <= k) {
            std::swap(a[i], a[k]);
            i++;
            k--;
        }
    }
    if (lo < k)                 // recurse
        QuickSort(a, lo, k);
    if (i < hi)
        QuickSort(a, i, hi);
}

【讨论】:

  • 嗯,这两个都比我的实现和 qsort 快...但是为什么呢?支点的选择真的那么重要吗?我想分区的函数调用也更少
  • @vexe - 对于伪随机数据,枢轴的选择无关紧要。我使用分区逻辑内联而不是单独的函数制作了您的代码的修改版本。它仍然较慢,很可能是由于我的示例包括检查值 > 枢轴(Hoare 分区方案)。查看quicksort 上的维基文章了解更多信息。
  • 我实际上只是在查看@vexe 的分区算法,因为我认为它在进行冗余比较。我认为这实际上并不是多余的比较问题,而是它不在乎它在哪里交换 到。向上/向下索引确保交换的两半都是有用的,而不是在递增pivotdest 的同时用自身交换元素。它也可以更快地停止一个元素,因为将枢轴与自身进行比较是没有用的。在已经排序的情况下,vexe 的分区会将 每个 元素与自身交换,因为 PD == i 每次迭代。
  • 维基文章继续提到,使用带有 n/5 因子的 median of medians 进行快速排序,可以将最坏情况减少到 O(n log(n)),但是这样做的开销会使其变慢在平均情况下。合并排序可能比使用中位数(n/5)的快速排序更快。如果有 16 个寄存器可用,例如 64 位模式下的 X86,那么使用大多数寄存器作为指针的 4 路合并排序大约与快速排序最佳情况一样快,而最坏情况则快得多。
  • @PeterCordes 不确定它是如何与自身交换元素的,你能举个带数字的例子吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-11
  • 2011-06-02
  • 2017-03-18
  • 2021-06-19
  • 2013-04-23
  • 2015-06-25
相关资源
最近更新 更多