两个个有趣的排序算法

算法 专栏收录该内容
5 篇文章 0 订阅

算法导论在介绍了几个基础的排序算法之后,用决策树的形式,总结出基于比较的排序算法,O(nlgn)就是在渐进性上的最优实现。但之后还介绍了两个在最坏情况下,以线性时间完成的排序算法。当然,他们不基于关键值的比较。

1,计数排序 (counting sort)

计数排序的思想:假设元素的关键值取值在一个固定的范围中,然后对每个元素,统计所有关键值小于该元素关键值的元素个数(这就是该排序方法名字含义),根据这个计数,可以以 O(1) 时间将一个元素定位到正确的序号。

算法的关键在于如何以O(n)时间完成对所有元素的前序个数的计数。因为所有的元素关键值取值在固定范围上,算法利用了这点。

具体的实现继承上一篇文章<几个基础的排序算法>中的MySort类,增加一个max_key属性

 

2,基于计数排序的基数排序 (radix sort)

所谓基数,是指将一个数分成d段,每段共有 Ri 个取值,Ri 称为该段的基数,每段的基数可以不相等。比如对4位十进制数xxxx,可以分为4段,每段基数都为10(0-9),也可以分为两段,每段基数都为100,也可以分为3段,基数分别为5(0-4),16(0-15),125(0-124)。

基数排序对输入数据进行如下方式的排序:从低位段开始,对输入数据以该段基数进行排序,每一遍在前一段排序的结果上进行,且以线性时间排序,即上面所介绍的计数排序(counting sort)。

对所有数据以每段基数先后排序的段的顺序,为什么从低位段开始?对两个数的比较,高位数大的数大于另一个数,从这个直觉来看可以先对数据以高位段线性排序,然后继续排序低位。但从计算机实现来看,这引起很多另外需要记录的空间来保存信息,比如只有高位相等的数需要重排,但有哪些数的高位相等,需要额外的记录,这可能需要很多数组又需要递归。从低位开始排序,可以避免这些麻烦。

以低位段开始排序数据,要求段排序算法(即计数排序),必须是稳定的。稳定性是排序算法的一个重要特点。如果两个相同的元素在排序前后都以相同的先后顺序出现,则这个排序算法就是稳定的。在基数排序中,如果段排序不稳定,基数排序的结果就会出错。比如,前 i - 1 次排序已经将前 i - 1 段的数据排好序,接着在第 i 次段排序中,第4个数和第5个数的段基数(第i段)相同,如果排序不稳定,那么这次段排序,可能会打乱已排好序的前 i - 1 段数据。从而也不能保证后续的排序正确。 在上面实现的计数排序count_sort中,如果将第4个for循环改为从0到 count - 1 递增,计数排序的结果依然正确,但是破坏了排序的稳定性,如果使用这个计数排序,就得不到正确的基数排序算法。

 

3 线性排序的神奇和先天不足

两个线性排序算法对数据的范围作了假设,计数排序假设最大值K,基数排序假设输入数据最多有d段,每段基数为Ri。

两个排序算法的时间复杂度和空间复杂度都是Θ(n),通过基数排序中的分段,可以将基数排序中的计数排序所需要的空间进行优化,同样的空间开销下,又可以将计数排序所允许的max_key值增大。如,32位的输入数据,不分段时,计数排序需要用大小为max_key 2^32的数组,分为4段8位,每次计数排序需要2^8 = 256大小的计数数组,总共需要4 * 256 = 1024的计数数组。

这个华而不实的时间复杂度就是线性排序的神奇之处。它并没有太大的吸引力。

计数排序在max_key不是很大的情况下,确实非常快,在公司的服务器上测试,100,000,000规模,范围在[1,30000000]的输入数据在25秒内完成排序,而快速排序对相同规模的数据排序时间历时近2分钟。但计数排序的不足显而易见,因为O(max_key + n)的空间复杂度的问题,它不能处理和快速排序一样的max_key的输入数据。

通过分段的基数排序,可以解决计数排序的这个不足,以下分析基于分段的基数排序,它又有了新的缺陷。

看起来,线性排序除了较大但仍可接受的空间开销外,在时间复杂度上比其他各种排序算法都好。但用复杂度表示的算法运行时间,只有在n足够大时,才能完全的体现出来。在小规模排序上, O(n^2)的插入排序都可以战胜O(nlgn)的随机选择快速排序,所以对小规模数据,O(n)的线性算法并不一定比O(nlgn)快。

据《算法导论》的解释,隐含在你T(n) = C'n + C1 = O(n) 里的常数C,比O(nlgn) : C" n lgn + C1 n ^ (-x) + C2的C大很多。从代码里看,观察3个和count有关的for循环,合并在一起组成一遍循环,循环体内要执行的指令数就是线性排序O(n)所隐含的常数C',C'涉及到3次data_array[i]读,2次c[i]读写,以及对b[i]的读写,涉及到回写的指令一般会引发更多的时钟周期,对快速排序的基本指令进行类似分析得出C",这里保守的假设 C' = 27 C"。(如果分段,这个常数还会随分段数倍增,因为 T(n) = d(n + k))

为简便且不失一般性,假设线性排序 Ta(n) = 27n,快速排序 Tb(n) = nlgn。看看n去多大时,Ta发挥出应有的优势。即当 27 < lgn 时,Ta(n) < Tb(n),此时,n > 2^27 > 10^8。

结论就是,当排序规模达到100000000时,线性排序的优势才开始发挥。在此之前,就算不谈线性排序Θ(n)的空间复杂度,线性排序也对快速排序毫无优势。或许将来计算机的发展(存储问题)超过了实际应用的需求(问题规模,max_ken)时,线性排序可以大显身手取代O(nlgn)。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值