快速排序与合并排序效果分析

时间:2018-10-02 04:11:46

标签: performance sorting quicksort mergesort

合并排序的最坏情况复杂度为O(logN),而快速排序的复杂度为O(N ^ 2),因此从理论上讲,合并排序比快速排序要好。但是我听说由于某些复制开销,大多数情况下快速排序的性能优于合并排序。 See the reference

然后我决定实施和测试,以下是我在C语言中的完整源代码,

来源

#include <stdio.h>
#include <time.h>

#define SZ 10000000
#define MOD 10000007
#define i64 long long int

i64 nums[SZ];

i64 L[SZ], R[SZ];

i64 seed = 0xff;
i64 srand(){
    seed = (seed + 17 * seed) % MOD;
    return seed;
}

void make(){
    for (register int i = 0; i < SZ; i++)
        nums[i] = srand() % MOD;
}

void swap(i64 *a, i64 *b){
    i64 t = *a;
    *a = *b;
    *b = t;
}

int pivote(int s, int e){

    //int p = s + srand() % (e - s + 1);
    int p = s + (e - s) / 2;
    //int p = s;
    //int p = e;

    i64 v = nums[p];
    int c = s;
    swap(nums + p, nums + e);
    for (register int i = s; i < e; i++){
        if (nums[i] < v){
            swap(nums + i, nums + c);
            c++;
        }
    }
    swap(nums + c, nums + e);
    return c;
}

void qsort(int s, int e){

    if (s < e){
        int p = pivote(s, e);
        qsort(s, p - 1);
        qsort(p + 1, e);
    }
}

void merge(i64 arr[], int l, int m, int r){
    int i, j, k;
    int n1 = m - l + 1;
    int n2 = r - m;

    for (i = 0; i < n1; i++)
        L[i] = arr[l + i];
    for (j = 0; j < n2; j++)
        R[j] = arr[m + 1 + j];

    i = 0;
    j = 0;
    k = l;
    while (i < n1 && j < n2)
    {
        if (L[i] <= R[j])
        {
            arr[k] = L[i];
            i++;
        }
        else
        {
            arr[k] = R[j];
            j++;
        }
        k++;
    }

    while (i < n1)
    {
        arr[k] = L[i];
        i++;
        k++;
    }

    while (j < n2)
    {
        arr[k] = R[j];
        j++;
        k++;
    }
}

void mergeSort(i64 arr[], int l, int r){
    if (l < r){
        int m = l + (r - l) / 2;

        mergeSort(arr, l, m);
        mergeSort(arr, m + 1, r);
        merge(arr, l, m, r);
    }
}


void testQsort(){
    double s, e;

    make();

    s = clock();
    qsort(0, SZ - 1);
    e = clock();
    printf("qsort random: %Lf ms\n", (e - s) / 1);

    s = clock();
    qsort(0, SZ - 1);
    e = clock();
    printf("qsort sorted: %Lf ms\n", (e - s) / 1);

}

void testMsort(){
    double s, e;

    make();

    s = clock();
    mergeSort(nums, 0, SZ - 1);
    e = clock();
    printf("msort random: %Lf ms\n", (e - s) / 1);

    s = clock();
    mergeSort(nums, 0, SZ - 1);
    e = clock();
    printf("msort sorted: %Lf ms\n", (e - s) / 1);
}

int main(){

    testMsort();
    testQsort();

    return 0;
}

一千万个元素的结果:

msort random: 4596.000000 ms
msort sorted: 3354.000000 ms
qsort random: 7637.000000 ms
qsort sorted: 5074.000000 ms

我使用了四种快速排序版本,

  • 在第一个位置旋转
  • 旋转到最后一个位置
  • 在中间位置旋转
  • 在随机位置旋转

快速排序的所有版本似乎都不胜过合并排序。 谁能说出为什么提到快速排序胜过合并排序吗?

我的快速排序实现有什么问题吗?

更新1

遵循下面提到的@rcgldr的答案,我已经测试了以下版本的快速排序,最终胜过了任何版本的合并排序。

void qsort3(int s, int e){
    if (s < e){
        i64 p = nums[(s + e) / 2];
        int i = s - 1;
        int j = e + 1;
        while (true){
            while (nums[++i] < p);
            while (nums[--j] > p);
            if (i >= j) break;
            swap(nums + i, nums + j);
        }
        qsort3(s, j);
        qsort3(j + 1, e);
    }
}

1 个答案:

答案 0 :(得分:1)

该问题的快速排序示例基于Lomuto分区方案,该方案比Hoare分区方案慢。链接到Hoare分区方案的示例:

QuickSort with middle elemenet as pivot

合并排序示例不断创建子数组并复制数据。一种更有效的方法是对数组进行一次分配,然后根据自上而下的合并排序的递归级别或自下而上的合并排序的通过次数来更改合并的方向。链接到显示自下而上和自上而下的合并排序的Java源代码。这可以很容易地转换为c:

'MergeSort Algorithm' - What's the better implementation in JAVA?

关于相对性能,一个简单的快速排序(如此答案中链接到的排序)比基本合并排序(用于对整数或浮点数之类的简单元素进行排序)大约高15%。但是,如果增强了快速排序以避免O(n ^ 2)的最坏情况下的时间复杂度,则优点会降低,并且主要优点是它不需要O(n)空间来合并合并所需的排序操作。通常,与快速排序相比,合并排序的动作更多,但比较却更少。如果对指向对象的指针数组进行排序,则比较开销将大于移动指针所需的时间,并且合并排序最终会更快。另一方面,对指向对象的指针数组进行排序涉及对这些对象的随机访问,这对缓存不友好,并且除非对象相当大,否则对对象进行排序比对指针进行排序要快得多(通常要权衡取舍128到256个字节,具体取决于系统)。