桶排序
前言:在博客写这些文章的目的用于记录所学,怕以后忘了,如果哪里写的不对欢迎指正,谢谢!!
学习目标:掌握桶排序算法的原理和思想
一、前提知识
排序算法概念、时间复杂度。可前往此网址 排序算法学习01_算法基础介绍阅读
二、桶排序介绍
桶排序(Bucket sort),桶排序是我们上篇讲过的计数排序的升级版,所以桶排序也是如计数排序一样,通过把原序列的元素放置到“一个新的位置”上,然后依次取出排序。
且桶排序也不是比较排序,桶排序是稳定的
三、桶排序工作原理
学过计数排序,我们知道,计数排序把原序列的元素,放到一个新的数组上,进行排序。
而升级后的桶排序,不再局限于一个数组,而是创建多个桶来放置原序列的元素,并为每一个桶设置一个范围,然后将原序列各个元素根据大小分布到专属的桶中,再然后按顺序依次从各个桶取出,则可得到一个排序好的结果
图解:
四、桶排序设计思路
一、升级后的桶排序,不像原先计数排序,按照索引来排序,而是在各个桶上设置一个范围,该范围将用来接收原序列中对应的元素,所以
-
首先要创建足够的桶,保证原序列的元素都可以存放。至少要大于数组长度
-
由于桶排序不是比较排序,那么我们需要一种区间(也就是范围),使从原序列进入桶的元素,该元素都能根据这个区间进入对应的桶
二、当原序列元素存入完毕时,依次取出时,还要对每个桶内部进行排序,比如上图中,如果14在11前面时,那就需要再对每个桶内部进行排序
- 所采用桶内部的排序方式将会直接影响到桶排序的性能
根据以上两大点设计方案:目前我所知两种方案,1.桶数量根据原序列长度设置 2.桶数量根据最大最小值差来设置
- 关于桶数量,我们此次选择使用原序列长度来设置。桶所采用的数据结构使用JDK提供的ArrayList类
- 两大优势:1.自动扩容
- 2.使用该容器最主要还是因为,该容器里还可以放置容器作为元素,这样就可以把多个桶归纳到一起方便处理
- 关于区间设置,这是桶排序最核心的问题了
- 区间的选择
- 使用原序列最大值减去最小值得到一个总区间
- 然后使用总区间除以原序列长度,即可让每个桶平均分配到一个区间
- 我们这时要再引入一个专业名词,用于数据入桶时的一个映射函数,该函数能帮助我们通过值的大小来找到对应区间所在桶。
- 关于映射函数,我目前所知有两种设置方式
- 第一种,就是我使用的这种方案
- 桶位置 == 待排入值 / 区间
- 第二种,桶的数量根据最大最小值来设置时 (max - min) / arr.length + 1;
- 桶位置 == (待排入值 - 序列最小值) / 序列长度
- 使用该方案不需要去设置区间
- 性能最优,但耗空间
- 区间的选择
- 关于桶内部排序,我这边直接使用JDK提供的工具类 Collections 的sort方法
五、代码实现
package com.migu.sortingAlgorithm;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class BucketSort {
public static void main(String[] args) {
int[] arr = {50,350,150,250,500,0};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr){
int max = arr[0]; // 最大
int min = arr[0]; // 最小
for(int i=1; i<arr.length; i++) {
if(arr[i] > max) {
max = arr[i];
}
if(arr[i] < min) {
min = arr[i];
}
}
// 原序列最大小元素差,用于分配区间
int differ = max - min;
// 每个桶的存数区间,向上取整
int section = (int)Math.ceil(differ / (arr.length - 1)) ;
// 桶列表
ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();
// 桶数量根据序列长度设置
for(int i = 0; i < arr.length; i++){
bucketList.add(new ArrayList<>());
}
// 遍历原序列值入桶
for(int i = 0; i < arr.length; i++){
// 当前数除以区间得出存放桶的位置 减1后得出桶的下标
int num = arr[i] / section - 1;
if(num < 0){
num = 0;
}
bucketList.get(num).add(arr[i]);
}
// 桶内排序
for(int i = 0; i < bucketList.size(); i++){
// 调用JDK工具类帮助排序
Collections.sort(bucketList.get(i));
}
// 写入原序列
int index = 0;
for(ArrayList<Integer> arrayList : bucketList){
for(int value : arrayList){
arr[index++] = value;
}
}
}
}
六、时间复杂度
只要合理分配桶的数量,时间复杂度为O(n + k),这个k就是桶内部排序的复杂度。如果在一个桶列表中,有一个桶存在大量数据的话,那么时间复杂度就是另一回事了
七、总结
在内存量足够的情况下,建议提升桶的数量,当然也要随之改变区间的分配程度,这样就可避免桶内的排序操作,时间复杂度可到O(n)。
桶排序对于分布不均匀的数据排序不友好,如果上面讲的一样,在某个区间有大量数据,就完全没有桶排序的意义了