算法渣-排序-桶排序

没有一身好内功,招式再多都是空;算法绝对是防身必备,面试时更是不可或缺;跟着算法渣一起从零学算法

线性排序

常见的三种以线性时间运行的算法:计数排序、基数排序和桶排序;网上教程不少,但三者经常混淆,称桶排序但实质可能是计数排序,为了保证原味性,主要参考《算法导论》

需要注意的是线性排序算法是非基于比较的排序算法,都有使用限制才能达到线性排序的效果

定义

桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))

算法

桶排序的思想:其实就是先分配再收集的这个一个过程

假设输入是一个随机过程产生的[0,1)区间上均匀分布的实数

把区间划分成n个大小相同的子区间,或称为桶。然后将n个输入元素分布的各个桶中去。先对各个桶中的数进行排序,然后按次序把各桶中的数据列出来即可

(因为输入元素均匀而独立的分布在区间 [0,1)上,所以不会出现很多数落在一个桶中的情况)

在桶排序算法中,假设输入的是一个含n个元素的数组A,且每个元素满足0≤A[i]<1。另外,还需要一个辅助数组B[0…n-1]来存放链表(桶),并假设可以用某种机制来维护这些表

【刚开始按照示例图的方式理解了桶排序,桶分10个,以十分位为桶号放入各个桶,也算是桶排序一种实现方式,但还是狭隘了】


在实际应用时,其实并不然必须元素范围为[0,1),整数,小数都是可以的,只要分布均匀就能最大发挥桶排序优势

优质的桶排序需要考虑几个因素:

  1. 桶的数量:桶越多,占用空间越大
  2. 区间跨度:桶与桶之间的跨度
  3. 桶内元素的排序

一般区间跨度:

除了最后一个桶只包含一个最大值之外,其余各桶之间的区间跨度=(最大值-最小值)/(桶数量-1)

1
2
3
4
5
6
7
8
//伪代码
BUCKET_SORT(A) 
n = length(A)   
for i= 1 to n       
do insert A[i] into list B   
for i=0 to n-1       
do sort list B[i] with insertion sort
concatenate the list B[0]、B[1],,,B[n-1] together in order

实现

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
private static void bucketSort(double [] array){
int bucketNum = 10;//10个桶
double max = Double.MIN_VALUE;
double min = Double.MIN_VALUE;
for (double a:array) {
if(a> max) {
max = a;
}
if(a<min) {
min = a;
}
}
//区间跨度
double span = (max - min) / (bucketNum - 1);
double [][]buckets = new double[bucketNum][];
//初始化桶
for (int b = 0; b<bucketNum;b++) {
//以排序元素个数初始化每个桶,以防极端情况
buckets[b] = new double[array.length];
}
//每个桶的元素数量
int [] index = new int[10];
for (int d = 0;d<array.length;d++) {
int bucket = (int)((array[d] - min)/span);
buckets[bucket][index[bucket]] = array[d];
index[bucket]++;
}

//每个桶排序,直接使用sort函数了
for(int b = 0; b<10; b++) {
Arrays.sort(buckets[b]);
}
int j = 0;
for(int b = 0; b<10; b++) {
if(index[b] == 0) {
continue;
}
//这儿需要特殊处理一下,主要是因为每个桶初始化了array.length,
// 经过sort排序,比如第一个桶数组变成了[0.0,0.0,......0.002]
//需要剔掉数组中的0
for (int bi = array.length-index[b];bi<array.length;bi++) {
array[j++] = buckets[b][bi];
}
}
}

复杂度

时间复杂度:

假设有m个桶,则每个桶的元素为n/m;

当辅助函数为冒泡排序O(n^2)时,桶排序为 O(n)+mO((n/m)2);

当辅助函数为快速排序时O(nlgn)时,桶排序为 O(n)+m*O(n/m log(n/m))

空间复杂度: 

O(M+N)

通常桶越多,执行效率越快,即省时间,但是桶越多,空间消耗就越大,是一种通过空间换时间的方式

公众号:码农戏码
欢迎关注微信公众号『码农戏码』