1 桶排序
桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。
2 原理
根据最大值最小值创建多个桶,确定各个桶之间的跨度,然后遍历原始数列,把各元素放到对应的桶中,先是每个桶内的元素各自排序,然后遍历输出所有的桶内的所有元素,输出结果即是排序好的数组。
桶排序有点像分治法,把一大堆数据分成若干个桶,每个桶内各自排序,最后再合并。
桶排序的一个要点就是如何确定桶的数量和桶与桶之间的跨度,在上面我看的程序员小灰的文章中他是创建与原始数组长度数量相等的桶,除了最后一个桶只包含一个最大值之外,其余各桶之间的区间跨度=(最大值-最小值)/(桶数量-1)。
假如有一个这样的数组:
[3.31, 2.26, 3.94, 3.67, 4.96, 2.12, 3.91, 3.72, 0.12, 3.2, 0.01, 3.5, 2.78, 1.21, 2.97, 2.64]
一共有 16 个元素,所以我们创建 16 个桶。最大值为 4.96,最小值为 0.01,所以每个桶之间的跨度为 (4.96-0.01)/(16-1)=0.33。
然后遍历原始数组,将各元素放到对应的桶中:
然后各个桶内各自排序:
最后遍历输出桶内元素即可。
3 代码实现
@Test
public void testBucketSort() {
double[] doubleArray = new double[new Random().nextInt(10) + 10];
for (int i = 0; i < doubleArray.length; i++) {
double random = new Random().nextDouble();
if (random > 0.5) {
random -= 0.5;
}
doubleArray[i] = random * 10;
doubleArray[i] = Double.valueOf(String.format("%.2f", doubleArray[i]));
}
bucketSort(doubleArray);
}
public static double[] bucketSort(double[] origin) {
if (origin == null || origin.length == 0) {
return new double[]{};
}
System.out.println("origin--->" + Arrays.toString(origin));
// 获取数组中最大值、最小值
double max = origin[0];
double min = origin[0];
for (int i = 0; i < origin.length; i++) {
if (origin[i] > max) {
max = origin[i];
}
if (origin[i] < min) {
min = origin[i];
}
}
System.out.println("max--->" + max);
System.out.println("min--->" + min);
System.out.println("origin.length--->" + origin.length);
// 创建桶,数量与原始数组的长度相同
ArrayList<LinkedList<Double>> bucketList = new ArrayList<>();
for (int i = 0; i < origin.length; i++) {
bucketList.add(new LinkedList<>());
}
// 每个桶的区间跨度
double span = (max - min) / (origin.length - 1);
System.out.println("span--->" + span);
// 将数据放到对应桶,最后一个桶永远只会存在一个元素,就是最大值
for (int i = 0; i < origin.length; i++) {
int bucketIndex = (int) ((origin[i] - min) / span);
bucketList.get(bucketIndex).add(origin[i]);
}
System.out.println("bucketList--->" + bucketList);
// 将每个桶内部排序,Collections.sort() 底层使用的是归并排序
for (int i = 0; i < bucketList.size(); i++) {
Collections.sort(bucketList.get(i));
}
System.out.println("bucketList--->" + bucketList);
// 遍历桶,所有元素已经从小到大排序号,输出所有元素即可
double[] sortedArray = new double[origin.length];
int index = 0;
for (List<Double> bucket : bucketList) {
for (Double d : bucket) {
sortedArray[index++] = d;
}
}
System.out.println("sortedArray--->" + Arrays.toString(sortedArray));
return origin;
}
结果如下:
4 总结
时间复杂度
假设原始数列有 n 个元素,分成 m 个桶,第一步求最大最小值时间复杂度为 O(n);第二步创建空桶时间复杂度为 O(m);第三步遍历原始数组时间复杂度为 O(n);第四步桶内各元素排序,归并排序时间复杂度为 O(nlogn),所以时间复杂度为 O(n/m*log(n/m)*m);第五步输出结果时间复杂度为 O(n),总的时间复杂度为 O(3n+m+n/m*log(n/m)*m)。去掉系数的话时间复杂度为 O(n+m+n(logn-logm))。
控件复杂度
假设原始数列有 n 个元素,分成 m 个桶,空间复杂度为 O(m+n)。
优点
1.当桶内元素分布均匀时,即 n=m 时,时间复杂度为 O(n)。
2.可以对小数进行排序。
缺点
1.当桶内元素分布极不均匀时时间复杂度为 O(nlogn),还会创建许多空桶,造成空间浪费。