一、桶排序
桶排序同样是一种线性时间的排序算法,类似于计数排序所创建的统计数组,桶排序需要创建若干个桶来协助排序。
桶(bucket) 代表一个区间范围,里面可以装载一个或者多个元素。
假设有一个非整数数列:
如下图
工作原理:
第一步:
创建桶,并确定每一个桶的区间范围。
具体创建多少个桶,如何确定桶的区间范围,有很多种不同的方式,这里创建桶的数量等于原始数列的元素数量,除最后一个桶只包含数列最大值外,前面各个桶的区间按照比例来确定
**区间跨度=(最大值-最小值)/(桶的数量-1)**
第二步:
遍历原始数列,把元素对号入座放入到各个桶中。
第三步
对每个桶内部的元素分别进行排序。
第四步
遍历所有的桶,输出所有元素。
到此为止,排序结束。
代码:
public class bucket {
public static double[] bucketSort(double[] array){
//1.得到数列的最大值和最小值,并计算出差值。d
double max = array[0];
double min = array[0];
for (int i = 1; i <array.length ; i++) {
if (array[i]>max){
max= array[i];
}
if (array[i]<min){
min = array[i];
}
}
double d =max - min;
//2,初始化桶
int bucketNum = array.length;
ArrayList<LinkedList<Double>> bucketList = new ArrayList<LinkedList<Double>>(bucketNum);
for (int j = 0; j <bucketNum ; j++) {
bucketList.add(new LinkedList<Double>());
}
//3,遍历原始数组,将每个元素放入桶中
for (int j = 0; j <array.length ; j++) {
int num = (int)((array[j]-min)*(bucketNum-1)/d);
bucketList.get(num).add(array[j]);
}
//4,对每个桶 内部进行排序
for (int j = 0; j <bucketList.size() ; j++) {
//JDK底层采用了归并排序或者归并的优化版本
Collections.sort(bucketList.get(j));
}
//5,输出全部元素
double[] sortArray = new double[array.length];
int index = 0 ;
for (LinkedList<Double> list :bucketList) {
for (double element:list) {
sortArray[index] = element;
index++;
}
}
return sortArray;
}
public static void main(String[] args) {
double[] array = new double[]{4.12,6.421,0.0023,3.0,2.123,8.122,4.12,10.09};
double[] sortArray = bucketSort(array);
System.out.println(Arrays.toString(sortArray));
}
}
结果:
在上述代码中,所有的桶都保存在ArrayList集合中,每一个桶都被定义成一个链表,这样便于在尾部插入元素。同时上述代码使用了JDK的集合工具类Collections.sort来为桶内部的元素进行排序。它的底层采用的是归并排序或Timsort,可以把他们当作一个时间复杂度为O(nlogn)的排序。
二,桶的时间复杂度
假设原始数列有n个元素,分成n个桶。
第一步,求数列最大、最小值。运算量为n.
第二步,创建空桶,运算量为n。
第三步,把原始数列的元素分配到各个桶中,运算量为n。
第四步,在每个桶内部做排序,在元素分布相对均匀的情况下,所有桶的运算量之和为n.
第五步,输出排序数列,运算量为n。
因此桶排序的时间复杂度为o(n)。同样空间复杂度也是o(n)。
当然桶排序并非绝对稳定,在极端情况下,第一个桶中有n-1个元素,最后一个桶中有1个元素,此时的时间复杂度将会退化为o(nlogn),并创建了许多空桶。因此需要看具体的场景来选择算法。
消化消化,对比一下之前所学习的排序。