我对快速排序的性能存在问题,涉及双重旋转策略。
情况:
取一个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;
}
答案 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毫秒
当然,与库函数的比较目前还不准确,因为我必须概括我的实现的数据类型。但是,这些结果仍然很有希望。