前言
桶排序及其应用
一、桶排序流程
相比其它排序,桶排序不基于比较的排序,根据数据状况来排序。应用范围不是很广。
(1)计数排序
定义一个数组,用来记录每个数出现的次数,数组下标就是值,然后根据排序规则,分别输出每种数的个数。最终就得到有序数组。
代码如下:
public class countSort {
public static void main(String[] args) {
int []arr={21,1,8,15,15,15,1,1,2,2,3,4,4,4,4};
System.out.println("排序前:"+Arrays.toString(arr));
countSort(arr);
System.out.println("排序后:"+Arrays.toString(arr));
}
public static void countSort(int[]arr){
int max=maxNum(arr);// 最大数
int[]help=new int[max+1];// 创建出最大数长度的辅助数组
for (int i=0;i<arr.length;i++){
help[arr[i]]+=1;// 计数
}
System.out.println("桶子:"+Arrays.toString(help));
int temp=0;
for (int j=0;j<help.length;j++){// 出桶
if (help[j]!=0){
for (int d=0;d<help[j];d++){
arr[temp++]=j;
}
}
}
}
// 找最大数
public static int maxNum(int []arr){
int max=0;
for (int i:arr){
max=Math.max(max,i);
}
return max;
}
}
时间复杂度:O(N)
结果如下:
(2)基数排序
准备十个桶,分别对应数字0~9,桶就是个容器,可以是队列,数组,栈等…,最开始将所有数按最多位的数以0补齐位数(【34,789,444】补全为:【034,789,444】),从个位数将数放入对应桶中(034就放入4号桶,789就放入9号桶),然后依次从0 ~ 9号桶的顺序取出一个桶里的遵循先进先出。然后从十位重复,最后到最高位,出桶后就得到有序数组。(优先级是从最高位开始依次递减)。
代码如下:
public class radixSort {
public static void main(String[] args) {
int []arr={63,21,80,91,23,42,444,382,45};
System.out.println("排序前:"+ Arrays.toString(arr));
radixSort(arr);
System.out.println("排序后:"+Arrays.toString(arr));
}
public static void radixSort(int []arr){
if (arr==null||arr.length<2){
return;
}
radixSort(arr,0,arr.length-1,maxbits(arr));
}
public static void radixSort(int []arr,int L,int R,int maxBit){
final int radix=10;// 10个桶子
int i=0,j=0;// j=>当前数的第几位
int []bucket=new int[R-L+1];// 辅助数组
for (int d=1;d<=maxBit;d++){ // 有多少位就进多少次
// 桶子共有maxBit个
// 桶子的计数与流程有差别,桶子计数的为小于等于当前下标的个数之和
// (第6个桶子是【0,1,2,3,4,5,6,7】的数字的总和)
int []count =new int [radix];
for (i=L;i<=R;i++){// 取出当前位的数字 入桶
j=getDigit(arr[i],d);
count[j]++;
}
for (i=1;i<radix;i++){// 桶子的累加和
count[i]=count[i]+count[i-1];
}
for (i=R;i>=L;i--){// 从右到左遍历,取出对应位 桶中对应数--,摆放位置 出桶
j=getDigit(arr[i],d);
bucket[count[j]-1]=arr[i];
count[j]--;
}
for (i=L,j=0;i<=R;i++,j++){// 将辅助数中的值给arr准备下一次进桶出桶
arr[i]=bucket[j];
}
}
}
// 取出一个数中的第d位数字
public static int getDigit(int x,int d){
return ((x/(int)(Math.pow(10,d-1)))%10);
}
// 找一个数组内最多有多少位
public static int maxbits(int []arr){
int max=Integer.MIN_VALUE;
for (int j : arr) {// 找出最大值
max = Math.max(max, j);
}
int res=0;
while (max!=0){// 循环计位数
res++;
max/=10;
}
return res;
}
}
结果如下:
二、排序算法的稳定性及其汇总
稳定性
同样的个体间,如果不因为排序而改变相对次序,就是这个排序是有稳定性的;否则就没有。
- 不具备稳定性的排序:
- 选择排序
- 快速排序
- 堆排序
- 具备稳定性的排序:
- 冒泡排序
- 插入排序
- 归并排序
- 一切桶排序思想下的排序
目前按没有找到时间复杂度O(N*logN),空间复杂度O(1)又稳定的排序算法。
时间复杂度 | 额外空间复杂度 | 稳定性 | |
---|---|---|---|
选择 | O(N^2) | O(1) | × |
冒泡 | O(N^2) | O(1) | √ |
插入 | O(N^2) | O(1) | √ |
归并 | O(N*logN) | O(N) | √ |
快排(随机) | O(N*logN) | O(logN) | × |
堆 | O(N*logN) | O(1) | × |
坑
- 归并排序的额外空间复杂度可以变成O(1)但会丧失稳定性,而且非常难(归并排序,内部缓存法)
- 原地归并排序,让时间复杂度变成O(N^2)
- 快速排序可以做到稳定,但空间复杂度会变为O(N),而且非常难
- 目前没有找到时间复杂度O(N*logN),额外空间按复杂度O(1),又稳定的排序。
- 奇数放在数组左边,偶数放在数组右边,还要求原始的相对次序不变。
工程上堆排序的改进
-
充分利用O(N*logN)和O(N^2)排序各自优势
在快排中,当样本容量小于60时使用插入排序更快(综合排序) -
稳定性考虑
在系统中使用排序,基础类型,系统会使用快排进行排序;而遇到非基础类型排序时,会使用归并保持稳定性。
总结
以上就是桶排序的所有内容。