桶排序每个桶存储一定范围的元素,通过映射函数,将待排序数组中的元素映射到各个对应的桶中,对每个桶中的元素进行排序,最后将非空桶中的元素逐个放入原序列中
其核心是将将数组分到有限数量的桶子里。每个桶子再分别排序。
桶排序过程中存在三个关键环节:
- 元素值域的划分,也就是元素到桶的映射规则。映射规则需要根据待排序集合的元素分布特性进行选择,若规则设计的过于模糊、宽泛,则可能导致待排序集合中所有元素全部映射到一个桶上,则桶排序向比较性质排序算法演变。若映射规则设计的过于具体、严苛,则可能导致待排序集合中每一个元素值映射到一个桶上,则桶排序向计数排序方式演化。
- 排序算法的选择,从待排序集合中元素映射到各个桶上的过程,并不存在元素的比较和交换操作,在对各个桶中元素进行排序时,可以自主选择合适的排序算法,桶排序算法的复杂度和稳定性,都根据选择的排序算法不同而不同
- 尽量保证元素分散均匀,否则当所有数据集中在同一个桶中时,桶排序失效
对于元素值域需要通过映射关系来确定,合理的映射关系,可以让数据分布在每个桶比较均匀。
方案一:设定一个固定比例,例如使用10倍于平均的容量。这在很多时候能够解决问题,但遇到极端数据的时候容易出现问题。
方案二:极端增加空间大小,使得每个桶固定装一个数,这需要限制输入数据不重复。但是,如果输入数据没有范围限制,我们必须申请Integer.MAX_VALUE字节数据,而这必然会导致内存过大。
方案三:使用最大值-最小值/ 数组长度+1 (max - min) / arr.length + 1;这种映射关系
可用于最大最小值相差较大的数据情况,要求数据的分布必须均匀,否则可能导致 数据都集中到一个桶中。
public static int[] bucketSort(int[] arr){
boolean isLegal=arr==null|| arr.length ==0;
if (isLegal) {
return arr;
}
int max= Arrays.stream(arr).max().getAsInt();
int min= Arrays.stream(arr).min().getAsInt();
//桶数
int bucketNum = (max - min) / arr.length + 1;//映射关系
ArrayList<Integer>[] bucketArr = new ArrayList[bucketNum];
Arrays.fill(bucketArr, new ArrayList<>());
//将每个元素放入桶
for (int j : arr) {
int num = (j - min) / (arr.length);
bucketArr[num].add(j);
}
//对每个桶进行排序
for (ArrayList<Integer> integers : bucketArr) {
Collections.sort(integers);
}
int arrNum=0;
for (ArrayList<Integer> list : bucketArr) {
for (Integer integer : list) {
arr[arrNum++]=integer;
}
}
return arr;
}
桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,如果数据共有N个,桶有M个,则桶排序平均复杂度为:
O(N)+O(N)+O((N/M)㏒(N/M))=O(N+N(logN-logM))=O(N+NlogN-NlogM)
最优情形下,桶排序的时间复杂度为O(n)。
由于需要申请额外的空间来保存元素,并申请额外的数组来存储每个桶,所以空间复杂度为 O(N+M)。算法的稳定性取决于对桶中元素排序时选择的排序算法。
桶排序需要消耗内存空间比较大,其本身桶里面也依赖其他排序算法的实现;所以一般不常用。更多的是基于桶排序的扩展—计数排序和基数排序