桶排序思想和计数排序比较相似,不同的是桶排序支持小数排序,为了支持小数排序,桶排序在操作的时候,为待排序数组构建一个List,而这个List的每个元素又是一个List,这每个List元素各自管理一段数值区间,当进行桶排序的时候,遍历待排序数组,将每个数放到对应的数值区间中,就像丢到不同的桶里一样,最后再对每个桶进行内部排序,最后遍历整个List,最终得到排序数组。
首先需要知道每个区间跨度多少合适,使用公式(最大值 - 最小值)/(桶数量 - 1)计算,这里桶数量等于待排序数组元素个数。
而每个元素应该放到哪个桶里,这里进行一个简单的公式推导:
- 设应该放到哪个桶为x,区间跨度为y,即将放入的元素为a,桶数量为b,数组最大值为max,最小值为min。
- 得到公式:y * x > a - min >= y * (x - 1)
- 对2.公式变形,得到 x > (a - min) / y >= x - 1
- 鉴于排序中并没有其他地方用到这个区间跨度变量,故将区间跨度公式代入整理后最终得到:
x > (a - min) * (b - 1) / (max - min) >= x - 1。
通过上面得到的公式,用最懒的强转int会舍掉小数后面所有值特性,直接得到桶的index,也就是那个x - 1。
桶排序代码如下:
public static double[] bucketSort(double[] array) {
//得到数列的最大值和最小值,并算出差值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;
//初始化桶
int bucketNum = array.length;
ArrayList<LinkedList<Double>> bucketList = new ArrayList<>(bucketNum);
for (int i = 0; i < bucketNum; i++) {
bucketList.add(new LinkedList<>());
}
//遍历原始数组,将每个元素放入桶中
for (double v : array) {
int num = (int) ((v - min) * (bucketNum - 1) / d);
bucketList.get(num).add(v);
}
//对每个桶内部进行排序
for (LinkedList<Double> doubles : bucketList) {
//JDK 底层采用了归并排序或归并的优化版本
Collections.sort(doubles);
}
//输出全部元素
double[] sortedArray = new double[array.length];
int index = 0;
for (LinkedList<Double> list : bucketList) {
for (double element : list) {
sortedArray[index] = element;
index++;
}
}
return sortedArray;
}