Quicksort,Dual Pivot,许多相同键的性能不佳

时间:2015-12-12 16:03:13

标签: c sorting quicksort

我对快速排序的性能存在问题,涉及双重旋转策略。

情况:

取一个n随机生成的32位整数数组,并按升序排列。让我们修复n = 10_000_000,然后在[0,MAX_RAND]中生成随机数。这里的一切都按预期工作 - 我的实现的性能与qsort()库实现的性能相当。

现在出现问题:

采用与上述相同的n但将随机数的intervall限制为[0,K] where K < ~5000,性能会大幅下降。我机器上的一些数字:

K = MAX_RAND
QS-DUAL-PIVOT时间:865毫秒
升序:1 QS_LIBRARY时间:1296毫秒
升序:1

K = 10000
QS-DUAL-PIVOT时间:2521ms
升序:1 QS_LIBRARY时间:1076毫秒
升序:1

K = 5000
QS-DUAL-PIVOT时间:4420ms
升序:1 QS_LIBRARY时间:1044毫秒
升序:1

我想,你明白了。它在我的私人英特尔机器上使用gcc-5.2编译,在Arch-Linux-64下使用-O9。

你有什么提示吗?问题是什么?

生成上述输出的示例代码:

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

int is_ascending (int *arr, int size)
{
    int i;

    for (i = 0; i < size - 1; i ++)
        if (arr[i] > arr[i + 1])
            return 0;

    return 1;
}


void quick_sort_dual_pivot (int *arr, int low, int high)
{
    int lt, ht, i, temp;

    if (low >= high)
        return;

    lt = low + 1;
    ht = high - 1;

    if (arr[low] > arr[high])
        {
        temp      = arr[low];
        arr[low]  = arr[high];
        arr[high] = temp;
        }

    if (low + 1 == high)
        return;

    for (i = low + 1; i <= high; ++i)
        {
        if (i > ht)
            break;

        if (arr[i] < arr[low])
            {
            temp = arr[lt];
            arr[lt] = arr[i];
            arr[i] = temp;
            ++lt;
            }
        else if (arr[i] > arr[high])
            {
            temp = arr[ht];
            arr[ht] = arr[i];
            arr[i] = temp;
            --ht;
            --i;
            }
        }

    ++ht;
    temp      = arr[ht];
    arr[ht]   = arr[high];
    arr[high] = temp;

    --lt;
    temp     = arr[lt];
    arr[lt]  = arr[low];
    arr[low] = temp;

    quick_sort_dual_pivot (arr, low, lt - 1);
    quick_sort_dual_pivot (arr, lt + 1, ht - 1);
    quick_sort_dual_pivot (arr, ht + 1, high);
}

int get_ms (int start)
{
    static struct timeval start_time;
           struct timeval end_time;
    int msec, seconds, useconds;

    if (start)
        {
        gettimeofday (&start_time, NULL);

        return 0;
        }

    gettimeofday (&end_time, NULL);

    seconds  = end_time.tv_sec  - start_time.tv_sec;
    useconds = end_time.tv_usec - start_time.tv_usec;

    msec = ((seconds) * 1000 + useconds / 1000.) + 0.5;

    return msec;
}


int comp_asc (const void *arg1, const void *arg2)
{
     return (*(int *)arg1) - (*(int *)arg2);
}


#define ARRAY_SIZE 10000000

int main(void)
{
   int i;
   int millisec;
   static int a1[ARRAY_SIZE];
   static int a2[ARRAY_SIZE];

   srand (time (NULL));

   for (i = 0; i < ARRAY_SIZE; i ++)
       {
       int r = rand () % 5000;
       a1[i] = r;
       a2[i] = r;
       }

   get_ms (1);
   quick_sort_dual_pivot (a1, 0, ARRAY_SIZE - 1);
   millisec = get_ms (0);
   printf ("QS-DUAL-PIVOT Time: %dms\n", millisec);
   printf ("Ascending:          %d\n", is_ascending (a1, ARRAY_SIZE));
   get_ms (1);
   qsort (a2, ARRAY_SIZE, sizeof (int), comp_asc);
   millisec = get_ms (0);
   printf ("QS_LIBRARY Time:    %dms\n", millisec);
   printf ("Ascending:          %d\n", is_ascending (a2, ARRAY_SIZE));

   return 0;
}

2 个答案:

答案 0 :(得分:1)

我建议对您的算法进行一次优化。

有些实例,其中arr[lt] == arr[ht]在分组之后和递归之前,在这种情况下整个数组部分2包含相同元素的重复项。这种特殊情况使得算法确实很慢,因为每个递归步骤的边界只会缩小2。此外,通过稍微更改递归部分可以轻松避免这种情况:

quick_sort_dual_pivot(arr, low, lt - 1);
if (arr[lt] != arr[ht])
    quick_sort_dual_pivot(arr, lt + 1, ht - 1);
quick_sort_dual_pivot(arr, ht + 1, high);

对我而言,至少它带来了相当大的加速。

This Answer帮助我理解了Dual Pivot Quicksort,并且包含了一个插图,显示了我对第2部分&#34;第2部分和第34部分的含义。另外,在评论中提到了我在这里建议的相同优化(尽管我自己在发现它之前没有读过它^^)

有关您的版本与增强版之间的比较,请参阅以下时间(QS-DUAL-PIVOT2)

使用int r = rand();

  

QS-DUAL-PIVOT时间:2216毫秒

     

升序:1

     

QS-DUAL-PIVOT2时间:640毫秒

     

升序:1

使用int r = rand() % 5000;

  

QS-DUAL-PIVOT时间:11185ms

     

升序:1

     

QS-DUAL-PIVOT2时间:530毫秒

     

升序:1

正如您所看到的,我的机器具有更高的执行基准时间,因此我很好奇它对您的案例的优化程度。

答案 1 :(得分:1)

结果太棒了!我还添加了插入排序扭曲,现在看到以下结果:

int r = rand ();
  

QS-DUAL-PIVOT时间:875毫秒
  QS-DUAL-PIVOT2时间:847毫秒
  QS-LIBRARY时间:1289ms

int r = rand () % 10000;
  

QS-DUAL-PIVOT时间:2511毫秒
  QS-DUAL-PIVOT2时间:513毫秒
  QS-LIBRARY时间:1080ms

int r = rand () % 5000;
  

QS-DUAL-PIVOT时间:4399毫秒
  QS-DUAL-PIVOT2时间:475ms
  QS-LIBRARY时间:1043毫秒

当然,与库函数的比较目前还不准确,因为我必须概括我的实现的数据类型。但是,这些结果仍然很有希望。