算法的天空

为了追寻多年前心中的理想,努力研究ACM相关各种知识

快速排序的那些事

快速排序是最经典的算法之一,应用无数,平均时间复杂度(nlgn),最差时间复杂度(n2)。空间复杂度主要看调用深度,平均O(lgn),最差O(n)。快速排序的分治思想可以用在很多地方,比如:一堆数中最大的几个数等。
通常在程序中直接使用库函数即可,C语言qsort,C++ sort。

一,C语言的qsort

用法,void qsort(void *base, int nelem, int width, int (*cmp)(const void *, const void *));
参数: 1,待排序数组的首地址; 2,数组中待排序元素数量;3,各个元素所占用空间的大小;4,指向函数的指针,用于定义排序的顺序
int comp(const void *a,const void *b)

例如:一个比较整数用的函数,

{
    return *(int *)a-*(int *)b;
}
一个比较结构体的函数,
int cmp(const void *a, const void *b)
{
    struct Sample *c = (Sample *)a;
    struct Sample *d = (Sample *)b;
    if(c->x!=d->x)
        return c->x - d->x;
    else
        return d->y-c->y;

}

具体调用:

        int a[N] = {10,9,8,7,6,5,4,3,2,1,0};

       qsort(a,10,sizeof(int),comp);

二,STL的sort函数

使用方法: sort函数可以传两个参数或三个参数。第一个参数是要排序的区间首地址,第二个参数是区间尾地址的下一地址,排序的区间是 [a,b) 。简单来说,有一个数组 int a[100] ,要对从 a[0] 到 a[99] 的元素进行排序,只要写 sort(a,a+100) 就行了,默认的排序方式是升序。
第三个参数是:STL的functional,默认是less,还可以是:
equal_to          相等
not_equal_to    不相等
less                 小于
greater             大于
less_equal        小于等于
greater_equal    大于等于

使用的时候要使用其重载函数,例如: less<int>, greater<double>。
对于用户的自定义类型,可以定义比较函数,或者重载"<"操作符。自定义比较函数的时候需要注意一点,否则debug运行时会出现:invalid operator<的错误消息。出现这个错误的原因是:比较函数中对于=的情况的处理,需要返回false。The STL algorithms for stable_sort() and sort() require the binary predicate to be strict weak ordering.For example: · Strict: pred(X, X) is always false。

三,C的qsort函数和STL的sort函数的性能对比

测试一,随机生成数据,Windows XP + VS 2010,在对随机数据排序时,STL的sort函数的速度要比qsort快。

测试二,对qsort的源码修改,它使用了模板,不需要传入函数指针,而且换掉了原来低效的逐个字节交换的swap函数,用iter_swap代替。经过对qsort函数进行修改之后,它竟然比sort函数快25.3%!这说明qsort函数的主要开销在于直接对字节指针的操作。这同时也说明,对于基本没有重复键的数据来说,qsort比sort要快。

测试三,我们再来比较一下特殊情况。使用系统自带的srand函数和rand函数,生成0~0x00007fff的数,这样1000000个数中就会每个数平均有31个重复值。我们看一下运行结果:

qsort     894 ms
m_qsort 519 ms
sort       554 ms
可以清楚地看到,在数据重复比较多的时候,sort的性能明显得到了提高。

测试四,考虑一种极限情况,所有数据相同,我们修改代码再运行一次:
qsort 72 ms
m_qsort 42 ms
sort 24 ms
这次很明显了,对于重复数据,sort函数的处理能力明显强于qsort。这主要是和sort函数三路划分分得更细致有关。

测试五,我们接着考虑递增和递减数组。修改代码然后测试,结果如下:
qsort 497 ms
m_qsort 234 ms
sort 203 ms
sort函数的运行时间比m_qsort要少。我们可以看到,相比随机数据,有序数据在快速排序的时候得到了很好的优化。再来看递减的数组。
qsort 534 ms
m_qsort 261 ms
sort 338 ms
 递减数组中sort函数略逊于qsort改进版,这大概是因为sort函数取样9个点造成过多交换开销造成的。

        从整体上看,我们得出这样一个结论:对于随机基本无重复的数据,qsort的改进版比sort函数优秀;而sort函数由于对分区比较细致,所以处理重复数据较多的数组则会比较优化。

四,快速排序实现的几个技巧

几个技巧:
一,quicksort的速度和partitioning element的选取有关系,尽量选取中间值会提高算法的效率。为了避免最坏情况的发生(对一个已经排好序的数组做逆序操作),可以选择第一个值,中间值和最后值,三个元素的中间值。(三元素中值法,STL中也使用了这种办法。)
二,std::sort用的快速排序和插入排序的结合。当元素个数是10~15且基本有序的情况下插入排序的效率较高。当大于这个数字的时候用的是快速排序。实际上快速排序分解成无数个递归,算法的最终必然会变成插入排序。
三,当排序数组中有大量重复元素的情况下,快速排序效率低。例如:加入数组中所有元素相等,快速排序扫描partition一次,得到的划分index为1,需要不停地循环,效率低下。
通过3路划分,可以得到以下效果图,之需要对less和greater部分进行排序即可。


五,快速排序的稳定性

排序稳定性的定义:通俗地讲就是保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj, Ai原来在位置前,排序后Ai还是要在Aj位置前。快速排序是一个非稳定的排序算法。例如:序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(下标从1开始计的第5个元素,出现的第三个3),交换就会把元素3的稳定性打乱(high--,3是其找到的第一个小于5的元素)。冒泡排序,插入排序都是稳定的排序算法。







阅读更多
个人分类: ACM算法
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭