线性排序-桶、基数,计数排序

桶、基数,计数排序都是针对海量数据的排序方法,时间复杂度为O(n)。

1. 桶排序

顾名思义,会用到“桶”,核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。

桶排序的限制条件。

a.要排序的数据需要很容易就能划分成 m 个桶,并且,桶与桶之间有着天然的大小顺序。这样每个桶内的数据都排序完之后,桶与桶之间的数据不需要再进行排序。

b.数据在各个桶之间的分布是比较均匀的。如果数据经过桶的划分之后,有些桶里的数据非常多,有些非常少,很不平均,那桶内数据排序的时间复杂度就不是常量级了。在极端情况下,如果数据都被划分到一个桶里,那就退化为 O(nlogn) 的排序算法了.

使用场景

外部排序中,即数据在存储在外部磁盘中,数据量很大,但我们的内存很小,无法将所有数据全部加载到内存中。比如说我们有 10GB 的订单数据,我们希望按订单金额(假设金额都是正整数)进行排序,但是我们的内存有限,只有几百 MB,没办法一次性把 10GB 的数据都加载到内存中。所以这时,就可以根据实际情况,先得出金额范围,比如最小是1元,最大是10万元,把所有订单分道100个桶中,第一个桶放1-1000元的订单,第二个桶放1001-2000元订单,以此类推。每一个桶对应一个物理文件。第一次遍历完了之后,在把每一个物理文件的内容加载到有限的内存中,然后用常见的排序算法(快排)来排序,最后再依次写入到一个文件中。最终存储的就是按照金额从小到大的订单数据了。

如果每个桶的数据不均匀,比如某个桶内数据很多,可以在把这个桶在细分成多个区间,直到当前内存可以装载为止。

2. 计数排序

计数排序其实是桶排序的一种特殊情况。当要排序的 n 个数据,所处的范围并不大的时候,比如最大值是 k,我们就可以把数据划分成 k 个桶。每个桶内的数据值都是相同的,省掉了桶内排序的时间。

比如100万个考生按照成绩排序,实际上考生的成绩只可能是0到750分,那我们就可以划分出751个桶,然后在把这100万个考生划分到这751个桶中,每个桶中的学生分数都是一样的,这就实现了100万个考生的排序,因为只涉及到一次遍历操作,所以时间复杂度为O(n)。

3. 基数排序

我们再来看这样一个排序问题。假设我们有 10 万个手机号码,希望将这 10 万个手机号码从小到大排序,你有什么比较快速的排序方法呢?快排的时间复杂度可以做到 O(nlogn),还有更高效的排序算法吗?桶排序、计数排序能派上用场吗?手机号码有 11 位,范围太大,显然不适合用这两种排序算法。针对这个排序问题,有没有时间复杂度是 O(n) 的算法呢?

假设要比较两个手机号码 a,b 的大小,如果在前面几位中,a 手机号码已经比 b 手机号码大了,那后面的几位就不用看了,所以按照位置先比较,如果有的场景他们的位数不同,可以先把他们按照某种方式补齐。

这里特别需要主要,每一位的排序必须采用稳定的排序算法,且从低位到高位一次排序,否则不会得到正确的结果。

 

思考题

假设我们现在需要对 D,a,F,B,c,A,z 这个字符串进行排序,要求将其中所有小写字母都排在大写字母的前面,但小写字母内部和大写字母内部不要求有序。比如经过排序之后为 a,c,z,D,F,B,A,这个如何来实现呢?如果字符串中存储的不仅有大小写字母,还有数字。要将小写字母的放到前面,大写字母放在最后,数字放在中间,不用排序算法,又该怎么解决呢?

way 1

用两个指针a、b:a指针从头开始往后遍历,遇到大写字母就停下,b从后往前遍历,遇到小写字母就停下,交换a、b指针对应的元素;重复如上过程,直到a、b指针相交。
对于小写字母放前面,数字放中间,大写字母放后面,可以先将数据分为小写字母和非小写字母两大类,进行如上交换后再在非小写字母区间内分为数字和大写字母做同样处理

way2

利用桶排序思想,弄小写,大写,数字三个桶,遍历一遍,都放进去,然后再从桶中取出来就行了。相当于遍历了两遍,复杂度O(n)

 

posted @ 2020-08-22 09:21  lswtianliang  阅读(159)  评论(0编辑  收藏  举报