这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战
桶排序
介绍
上篇文章我们讲解了计数排序,一个基于非比较的排序函数,很可惜的是,它只支持整数的排序。今天我们来介绍一种排序算法,它也是基于非比较的排序算法,并且其时间复杂度是O(n+k),k为 n*logn-n*logm,它就是桶排序。
贴上百度百科的解释:
桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是 鸽巢排序 的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。
值得注意的是,桶排序可以是稳定也可以是不稳定,取决于你用什么排序算法对每个桶进行排序,例如使用插入排序,那么桶排序就是稳定的。
算法思想
桶排序为什么能够对非整数进行排序呢?所谓的桶,又是什么概念呢?其实每个桶就代表一个区间范围,里面可以承载一个或多个元素。
桶排序的第一步,就是创建这些桶,确定每一个桶的区间范围,具体创建多少桶,如何确定桶的区间范围,有很多不同的方式,具体要看实际的业务场景,为了提高桶排序的效率,我们应该做到以下两点:
1、在空间允许的情况,尽量增加桶的数量
2、M个元素能够均匀地分到K个桶中
同时,对于桶中元素的排序,使用哪种排序算法对性能也有很大的影响。
我们这里创建的桶数量等于等于原始数列的元素数量,除了最后一个桶只包含数列的最大值,前面各个桶的区间按照比例确定。
区间跨度 = (最大值 - 最小值)/ (桶的数量 - 1)
第二步,遍历原始数列,把元素对号入座放入各个桶中:
第三步,每个桶内部的元素分别排序:
第四步,遍历所有的桶,输出所有元素:
0.5,0.84,2.18,3.25,4.5
到此为止,排序结束。
代码贴贴:
public void sort(double[] nums){
double max = nums[0],min = nums[0];
//求出数组中最大值和最小值
for(int i=1;i<nums.length;++i){
if(max < nums[i]) max = nums[i];
if(min > nums[i]) min = nums[i];
}
//初始化桶,桶的数量跟元素个数相同
int bucketNums = nums.length;
ArrayList<LinkedList<Double>> bucketlist = new ArrayList<>(bucketNums);
for(int i=0;i<bucketNums;++i){
bucketlist.add(new LinkedList<>());
}
//遍历原始数组,将每个元素放入桶中
for(int i=0;i<nums.length;++i){
int index = (int)((nums[i] - min) * (bucketNums - 1)/ (max - min));
bucketlist.get(index).add(nums[i]);
}
//对每个通内部进行排序
for(int i=0;i<bucketlist.size();++i){
Collections.sort(bucketlist.get(i));
}
//将每个桶中排好序的元素赋值回原数组
int cur = 0;
for(int i=0;i<bucketlist.size();++i){
LinkedList<Double> list = bucketlist.get(i);
for(int j=0;j<list.size();++j){
nums[cur++] = list.get(j);
}
}
}
性能分析
桶排序在性能上并非绝对稳定,理想情况下,桶中的元素分布均匀,当元素个数等于桶数时,时间复杂度可以到O(n)。但是,如果桶内元素的分布极不均衡,极端情况下第一个桶中有n-1个元素,最后一个桶中有一个元素。此时的时间复杂度将退化为O(nlogn),还白白创建了许多空桶,如下图:
结尾
本文介绍了桶排序的概念以及算法思路,相比于计数排序来说,桶排序能够对非整型的数据进行排序,桶排序是一种以空间换时间的非比较算法,当桶的数量跟元素的个数相同并且元素均匀地分配到这些桶的时候,其时间复杂度能够达到O(N),也就是少了对桶进行排序的步骤,在一些极端情况,桶的时间复杂度会降到O(nlogn),还白白浪费了空间。由此可见,没有最好的算法,只有适合的算法,关键看具体的应用场景。