桶排序(Bucket Sort)
桶排序是计数排序的升级版。顾名思义,会使用「桶」,核心思想就是 把要排序的的数据分到几个有序的桶里,每个桶里面的数据在单独进行排序,所有的桶内数据排序完成后,再按照桶的顺序依次取出,组成的序列就是有序的。
为了桶排序的高效,我们需要做到以下两点:
1.在额外空间充足的情况下,尽量增加桶的数量。
2.使用的映射函数能够把输入的 n 个数据均匀的分配到 k 个桶中。
同时,对于桶内元素的排序,选择哪一种排序算法对于性能的影响也至关重要。
如何把百万级别的订单根据金额排序
适用场景
比较适合用在外部排序中。所谓的外部排序就是数据存储在外部磁盘中,数据量比较大而内存有限,无法一次性全部加载到内存中。
比如说我们有 10GB 的订单数据,我们希望按订单金额(假设金额都是正整数)进行排序,但是我们的内存有限,只有几百 MB,没办法一次性把 10GB 的数据都加载到内存中。这个时候该怎么办呢?
订单金额最小是 1 元,最大是 10 万元。我们将所有订单根据金额划分到 100 个桶里,第一个桶我们存储金额在 1 元到 1000 元之内的订单,第二桶存储金额在 1001 元到 2000 元之内的订单,以此类推。每一个桶对应一个文件,并且按照金额范围的大小顺序编号命名(00,01,02…99)。
理想的情况下,如果订单金额在 1 到 10 万之间均匀分布,那订单会被均匀划分到 100 个文件中,每个小文件中存储大约 100MB 的订单数据,我们就可以将这 100 个小文件依次放到内存中,用快排来排序。等所有文件都排好序之后,我们只需要按照文件编号,从小到大依次读取每个小文件中的订单数据,并将其写入到一个文件中,那这个文件中存储的就是按照金额从小到大排序的订单数据了。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| const int len=sizeof(a)/sizeof(int);
void bucketSort(int a[]) { int b[10][len+1]={0};
int digits=numOfDigits(a); for(int i=1;i<=digits;i++) { distributeElments(a,b,i); collectElments(a,b); if(i!=digits) zeroBucket(b); } }
int numOfDigits(int a[]) { int largest=0; for(int i=0;i<len;i++) { if(a[i]>largest) largest=a[i];
int digits=0; while(largest) { digits++; largest/=10; } } return digits; }
void distributeElments(int a[],int b[10][len+1],int digits) { int divisor=10; for(int i=1;i<digits;i++) divisor*=10; for(int j=0;j<len;j++) { int numOfDigist=(a[j]%divisor-a[j]%(divisor/10))/(divisor/10); int num=++b[numOfDigist][0]; b[numOfDigist][num]=a[j]; } }
void collectElments(int a[],int b[10][len+1]) { int k=0; for(int i=0;i<10;i++) (int j=1;j<=b[i][0];j++) a[k++]=b[i][j]; }
void zeroBucket(int b[][len+1]) { for(int i=0;i<10;i++) for(int j=0;j<len+1;j++) b[i][j]=0; }
|
总结
快排、归并是经济实用型小轿车。而桶排序、计数排序、基数排序则是赛道上的跑车竞速。