一看就懂的大数据排序算法:如何给100万用户数据排序?
@[toc]
之前写过一篇八种排序算法的博客,不过都是基于小数据量进行的排序,没有像这篇这样做大数据排序。文末会放出链接。
桶排序(Bucket sort)
首先,我们来看桶排序。桶排序,顾名思义,会用到“桶”,核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。
看图说话啊。
桶排序的时间复杂度,是O(n),如果不出意外的话。
如果要排序的数据有 n 个,我们把它们均匀地划分到 m 个桶内,每个桶里就有 k=n/m 个元素。每个桶内部使用快速排序,时间复杂度为 O(k * logk)。m 个桶排序的时间复杂度就是 O(m * k * logk),因为 k=n/m,所以整个桶排序的时间复杂度就是 O(n*log(n/m))。当桶的个数 m 接近数据个数 n 时,log(n/m) 就是一个非常小的常量,这个时候桶排序的时间复杂度接近 O(n)。
那既然桶排序这么的优秀,为什么我们在平时的使用中却偏向于其他的排序方法呢(大多数情况下偏向于时间复杂度为O(nlogn)的快排)?
桶排序的小缺点
桶排序对要排序数据的要求是非常苛刻的。
首先,要排序的数据需要很容易就能划分成 m 个桶,并且,桶与桶之间有着天然的大小顺序。
其次,数据在各个桶之间的分布是比较均匀的。如果数据经过桶的划分之后,有些桶里的数据非常多,有些非常少,很不平均,那桶内数据排序的时间复杂度就不是常量级了。
桶排序比较适合用在外部排序中。所谓的外部排序就是数据存储在外部磁盘中,数据量比较大,内存有限,无法将数据全部加载到内存中。
比如说我们有 10GB 的数据,我们希望对这波数据进行排序,但是我们的内存有限,只有1G,没办法一次性把 10GB 的数据都加载到内存中。这个时候该怎么办呢?
我们可以先扫描一遍文件,看数据所处的数据范围。假设经过扫描之后我们得到,数据最小为1,最大为1000。我们将所有数据划分到 100 个桶里,第一个桶我们存储在 1 元到 10 元之内的数据,第二桶存储在 11 元到 20 元之内的数据,以此类推。每一个桶对应一个文件,并且按照数据范围的大小顺序编号命名(00,01,02…99)。
理想的情况下,如果数据均匀分布,那数据会被均匀划分到 100 个文件中,每个小文件中存储大约 100MB 的数据,我们就可以将这 100 个小文件依次放到内存中,用快排来排序。等所有文件都排好序之后,我们只需要按照文件编号,从小到大依次读取每个小文件中的数据,并将其写入到一个文件中。
不过呢,不均匀才是常态嘛,有可能某个区间的数据特别多,划分之后对应的文件就会很大,没法一次性读入内存。这又该怎么办呢?
针对这些划分之后还是比较大的文件,我们可以继续划分。
如果划分之后,数据还是太多,无法一次性读入内存,那就继续再划分,直到所有的文件都能读入内存为止。
计数排序(Counting sort)
计数排序其实是桶排序的一种特殊情况。
还是上面那个例子来说,区间跨度为1000,那就分它一千个桶,每一个数据位一个桶。
我们就当这波数据都是整数,所以并不需要再进行排序。我们只需要依次扫描每个桶,将桶内的数据依次输出到一个文件中,就实现了 10G 数据的排序。因为只涉及扫描遍历操作,所以时间复杂度是 O(n)。
计数排序的小缺点
那这不也很明显嘛,我桶排序就开100个桶,运气不好多开几个,计数排序一上来就是1000个桶预订了,其中会有多少浪费,多少空桶,谁知道呢?
计数排序只能用在数据范围不大的场景中,如果数据范围 k 比要排序的数据 n 大很多,就不适合用计数排序了。而且,计数排序比较适合给非负整数排序(不然刚刚为什么要假设),如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下,转化为非负整数。
基数排序(Radix sort)
我们再来看这样一个排序问题。假设我们有 10 万个手机号码,希望将这 10 万个手机号码从小到大排序,你有什么比较快速的排序方法呢?
这十一位的数,桶一个我看看?
不好分桶吧,跨度太大了。
我们可以用这样一种方法:
先用第一位来进行排序,然后第二位,第三位,··· ,第十一位。
根据每一位来排序,我们可以用刚讲过的桶排序或者计数排序,它们的时间复杂度可以做到 O(n)。如果要排序的数据有 k 位,那我们就需要 k 次桶排序或者计数排序,总的时间复杂度是 O(k*n)。当 k 不大的时候,比如手机号码排序的例子,k 最大就是 11,所以基数排序的时间复杂度就近似于 O(n)。
但是,耗桶。
实际上,有时候要排序的数据并不都是等长的
这时候怎么办呢?自己想想嘛,什么都让我说完了就没意思了。
基数排序的“脾气”
基数排序对要排序的数据是有要求的,需要可以分割出独立的“位”来比较,而且位之间有递进的关系,如果 a 数据的高位比 b 数据大,那剩下的低位就不用比较了。除此之外,每一位的数据范围不能太大,要可以用线性排序算法来排序,否则,基数排序的时间复杂度就无法做到 O(n) 了。
- 点赞
- 收藏
- 关注作者
评论(0)