算法简介
桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排
算法描述和实现
具体算法描述如下:
- <1>.设置一个定量的数组当作空桶;
- <2>.遍历输入数据,并且把数据一个一个放到对应的桶里去;
- <3>.对每个不是空的桶进行排序;
- <4>.从不是空的桶里把排好序的数据拼接起来。
Javascript代码实现:
/*方法说明:桶排序
@param array 数组
@param num 桶的数量*/
function bucketSort(array, num) {
if (array.length <= 1) {
return array;
}
var len = array.length, buckets = [], result = [], min = max = array[0], regex = '/^[1-9]+[0-9]*$/', space, n = 0;
num = num || ((num > 1 && regex.test(num)) ? num : 10);
console.time('桶排序耗时');
for (var i = 1; i < len; i++) {
min = min <= array[i] ? min : array[i];
max = max >= array[i] ? max : array[i];
}
space = (max - min + 1) / num;
for (var j = 0; j < len; j++) {
var index = Math.floor((array[j] - min) / space);
if (buckets[index]) { // 非空桶,插入排序
var k = buckets[index].length - 1;
while (k >= 0 && buckets[index][k] > array[j]) {
buckets[index][k + 1] = buckets[index][k];
k--;
}
buckets[index][k + 1] = array[j];
} else { //空桶,初始化
buckets[index] = [];
buckets[index].push(array[j]);
}
}
while (n < num) {
result = result.concat(buckets[n]);
n++;
}
console.timeEnd('桶排序耗时');
return result;
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(bucketSort(arr,4));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
JAVA:
public static void bucketSort(int[] arr){
//分桶,这里采用映射函数f(x)=x/10。
//输入数据为0~99之间的数字
int bucketCount =10;
Integer[][] bucket = new Integer[bucketCount][arr.length]; //Integer初始为null,以与数字0区别。
for (int i=0; i<arr.length; i++){
int quotient = arr[i]/10; //这里即是使用f(x)
for (int j=0; j<arr.length; j++){
if (bucket[quotient][j]==null){
bucket[quotient][j]=arr[i];
break;
}
}
}
//小桶排序
for (int i=0; i<bucket.length; i++){
//insertion sort
for (int j=1; j<bucket[i].length; ++j){
if(bucket[i][j]==null){
break;
}
int value = bucket[i][j];
int position=j;
while (position>0 && bucket[i][position-1]>value){
bucket[i][position] = bucket[i][position-1];
position--;
}
bucket[i][position] = value;
}
}
//输出
for (int i=0, index=0; i<bucket.length; i++){
for (int j=0; j<bucket[i].length; j++){
if (bucket[i][j]!=null){
arr[index] = bucket[i][j];
index++;
}
else{
break;
}
}
}
}
算法性能/复杂度
桶排序的时间复杂度可以从每一步分开分析。
1.分桶的过程,遍历每个元素、计算f(x),将x放到桶中,共3n次计算,显然是O(n)复杂度;
2.最后输出也是O(n)复杂度;
3.关键是小桶内排序的过程:即使使用先进的比较排序算法,也不可绕开O(n㏒n)的下限。因此,每个小桶的内部复杂度为n(k㏒k),总得复杂度为∑(ki*㏒ki)[i=1...m],其中m为桶的个数,ki为每个桶的元素数。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN)了)。因此,有两种方法:
1)使用更为平均的划分,使得不至于某个小桶的数据极多;
2)使用更多的桶,以减少每个桶数据的数量。极限状况下,每个桶只有一个数据,这样就完全没有比较操作。但是,在数据极多的情况下,这样是非常不现实的,会造成严重的空间消耗。这时候就需要权衡时空间复杂度了。
总结起来,设数据共有N个,桶有M个,则桶排序平均复杂度为:
O(N)+O(N)+O((N/M)*㏒(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)
最优情形下,桶排序的时间复杂度为O(n)。
桶排序的空间复杂度通常是比较高的,额外开销为O(N+M)(因为要维护M个数组的引用)。
算法稳定性
可以看出,在分桶和从桶依次输出的过程是稳定的。但是,由于我们在第3步使用了其他算法,所以,桶排序的稳定性依赖于这一步。如果我们使用了快排,显然,算法是不稳定的。
算法适用场景
桶排序在数据量非常大,而空间相对充裕的时候是很实用的,可以大大降低算法的运算数量级。此外,在解决一些特殊问题的时候,桶排序可能会起到意想不到的结果。参考资料中列出了一种。
示意图:
桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。
- 最佳情况:T(n) = O(n+k)
- 最差情况:T(n) = O(n+k)
- 平均情况:T(n) = O(n2)