标签:
在前面的文章中,我们介绍的都是基于比较的排序。
对于比较排序,对含n个元素的序列进行排序,在最坏情况下都要用O(n logn)次比较(归并排序和堆排序是渐近最优的)。
本文将继续介绍以线性时间运行的排序算法,他们使用的是非比较排序,因此下界O(n logn)对它们不适用。
想象下面这种情况:
一个班有k个人,需要排成一条纵队,地面上已经用粉笔按从小到大的顺序标明了1到k个号码,要求按身高从低到高排列,也就是说,最高的站在标号为k的位置,最矮的站在标号为1的位置。
那么对于每个人,如何知道自己的位置呢?
假如每个人都知道所有人的身高,那么他只需要数一数比自己矮的有多少个,比如10个,那么他就可以很自觉地站到标号为11的位置了。
注意:如果有身高相等的情况,我们需要改变下策略:每个人数一数比自己矮的以及和自己身高一样的人数(包括自己),假如有2个人身高相等,一开始他们得到的计数为8,于是一个人开始站到标号为8的位置,那么另一个人应该站到标号为7的位置(也就是说他的计数值应该减1)。
当每个人都站好之后,整个队就按身高从低到高排好序了。
计数排序假设n个输入元素都是0到k之间的整数,基本思想是对每个元素x,确定出小于x的元素个数,之后便可以将x直接放到合适的位置。(需注意元素相等的情况)
为完成计数排序,我们需要三个数组:输入数组a[0…n-1],排序结果数组b[0…n-1],计数数组c[0…k]。
算法步骤如下:
第四步有点绕,我们一步步看: a[i]表示第i个待排元素, c[ a[i] ]表示小于等于第i个待排元素的元素个数,也就是说,第i个待排元素应该放置的位置。之后c[ a[i] ]–,因为a[i]已经放到正确的位置上了,这一个语句使得下一个值等于a[i]的待排元素放置到a[i]的前一个位置,这就解决了相等元素的问题。(同时注意到i是逆序的,它保证了计数排序的稳定性,假如是正序的,以{1,1,3}为例,那么第一个1将放置到第二个1后面,不稳定)
代码实现:
void countingSort(int a[], int b[], int size, int k)
{
if (NULL == a || size <= 0)
return ;
int *c = new int[k+1];
if (NULL == c)
{
cerr << "new error" << endl;
return ;
}
memset(c, sizeof(c), 0);
for (int i = 0; i < size; ++i)
++c[ a[i] ];
for (int i = 1; i <= k; ++i)
c[i] += c[i-1];
for (int i = size-1; i >=0; --i)
b[ c[ a[i] ]-- ] = a[i];
}
计数排序花费的总的时间为O(k+n),当k = O(n)时,运行时间为O(n)。
可以看到,计数排序的效率是很高的,但是其局限性也高:排序的元素必须是【正整数】,且由于需要辅助数组c[0…k],所以元素的范围不能太大。
基数排序就很有意思了,它最初是用在老式穿卡机上的算法。
先举个扑克牌的例子……
我们要对一副扑克牌排序,可以这样做:先不管牌的大小,先对花色(比如红桃、黑桃、方块、梅花)排序,之后再根据牌的大小排序,于是一副牌就按花色、大小排好了。
对于序列:{329,457,657,839,436,720,355},基数排序的工作原理如下:
先按最低位排序:
720
355
436
457
657
329
839
再按次低位排序:
720
329
436
839
355
457
657
最后按最高位排序:
329
355
436
457
657
720
839
需要强调的一点:每一次按位排序都要是稳定的。
伪代码:
void radixSort(int arr[], int d) //d为最高位,1为最低位
{
for (int i = 1; i <= d; ++i)
use a stable sort to sort array on digit i;
}
以上的基数排序是最低位优先(Least Significant Digit first)的,简称LSD法,同样还有最高位优先(Most Significant Digit first)法,简称MSD法,它从最高位开始排序。
**以上代码考虑的是正整数,在实际应用中,可能需要处理负数的情况。
基数排序的时间复杂度取决于选择哪种稳定的中间排序算法。
桶排序就很好理解了,其思想是:假设所有元素均匀分布在区间[0,1)上,将该区间划分成n个相同大小的子区间(称为桶),之后,将相应的元素放到对应范围的桶里面,对各个桶里的元素进行排序,最后按次序把各个桶里的元素列出来即可。
桶排序的期望运行时间为O(n)。
以上只是最基本的桶排序思想,在实际应用中,考虑到实际数据的情况,需要做相应的调整。(比如桶的个数、桶之间是否平均分配等等),有兴趣的读者可以自行深入学习一下(对于并行算法感兴趣的朋友,可以看一看基于桶排序的并行化算法Sample Sort)。
位图排序通常并不作为一种排序算法列出来,因为它的局限性很强。不过在这里我仍然想提一下这个优秀的想法,因为没有哪一个算法是全能的,更多的时候我们应该根据具体的问题来选择最适合的算法,而不是一味地追求所谓的最快or最通用的算法。
关于位图排序,我之前写过一遍博文介绍过,这里不赘述,请有兴趣的读者移步:
【编程珠玑-读书笔记】用位图解决排序问题–仔细分析问题的重要性
到此为止,我们的排序算法将告一段落了,后面我将就排序算法写一篇文总结一下,除了目前为止我提到的算法,其实还有一些其他的排序算法,特别是一些并行化的排序算法,下篇文我会顺带介绍一下~
参考文献:《算法导论》 第八章 线性时间排序
每天进步一点点,Come on!
(●’?’●)
本人水平有限,如文章内容有错漏之处,敬请各位读者指出,谢谢!
标签:
原文地址:http://blog.csdn.net/jiange_zh/article/details/50703949