不基于比较的排序(桶排序)和算法的稳定性
桶排序思想下的排序
1、计数排序
2、基数排序
分析:
1、桶排序思想下的排序都是不基于比较的排序
2、时间复杂度为O(N),额外空间负载度O(M)
3、应用范围有限,需要样本的数据状况满足桶的划分
桶排序的流程
1、有十个桶,分别代表进制位上的数字为多少
2、看看数组中所有的数,最大的数的位数,作为循环的次数
3、找到相同进制位 位数为0-9的数字,分别入桶,再出桶,再找下一个进制位,入桶,出桶
4、直到找到最后一个进制位,入桶,出桶,就能排好序
变成代码的思想如下
public class RadixSort {
public static void radixSort(int arr[]){
//如果数组为空或者只有一个数,自动就不用排序
if(arr == null || arr.length<2){
return;
}
//排除了上面的情况,开始进行桶排序
radixSort(arr,0,arr.length-1,maxbits(arr));
}
//此方法时进行桶排序,此时参数L是0,R是数组长度,也可以进行自定义哪一段进行排序
private static void radixSort(int arr[],int L, int R, int digit) {
//常量代表,十个桶,也就是十进制的0~9
final int radix = 10;
//循环初始的数
int i = 0,j = 0;
//有多少个数准备多少个辅助空间
int[] bucket = new int[R - L + 1];
for(int d = 1;d <= digit;d++){//有多少位就进出几次
//10个空间
//count[0] 当前位(d位)是0的数字有多少个
//count[1] 当前位(d位)是(0和1)的数字有多少个
//count[2] 当前位(d位)是(0、1和2)的数字有多少个
//count[i] 当前位(d位)是(0~i)的数字有多少个
int[] count = new int[radix];//count[0..9]
//相同位上的相同的数累加
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[i] = bucket[j];
}
}
}
//此方法是获取对应位置上的数值是多少
public static int getDigit(int x,int d){
int j =0;
for(int i =0;i<d;i++){
j = x/10;
}
return j%10;
}
//此方法时找出数组中最大数的位数
public static int maxbits(int[] arr) {
int max = Integer.MIN_VALUE;
for(int i = 0;i < arr.length;i++){
max = Math.max(max,arr[i]);
}
int res = 0;
while(max != 0){
res++;
max /=10;
}
return res;
}
}
算法的稳定性
同样值的个体之间,如果不因为排序改变相对次序,就是这个排序是有稳定性的;否则就没有
不具备稳定性的排序:
选择排序、快速排序、堆排序
具备稳定性的排序:
冒泡排序、插入排序、归并排序、一切桶排序思想下的排序
目前没有找到时间复杂度O(N*logN),额外空间复杂度O(1),又稳定的排序
稳定性是指:比如说,排好序的1 1 1 2 2 2 2 3 3 ,再进行排序的时候,不会因为再次排序,导致2 2 2 2这几个2的顺序
每个算法时间复杂度、空间复杂度、稳定性的比较
时间复杂度 | 空间复杂度 | 稳定性 | |
---|---|---|---|
选择排序 | 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^2) | O(1) | × |
常见的坑
1、归并排序的额外空间复杂度可以变成O(1),但是非常难,不需要掌握,有兴趣可以搜"归并排序 内部缓存法"
2、"原地归并排序"的帖子都是垃圾,会让归并排序的时间复杂度变成O(N^2)
3、快速排序可以做到稳定性问题,但是非常难,不需要掌握,可以搜"01 stable sort"
4、所有的改进都不重要,因为目前没有找到时间复杂度O(N*logN),额外空间复杂度O(1),又稳定的排序
5、有一道题目,是奇数放在数组左边,偶数的放在数组右边,还要求原始的相对次序不变,碰到这个问题,可以怼面试官
工程上对排序的改进
1、充分利用O(N*logN)和O(N^2)排序各自的优势
2、稳定性考虑
3、改进,快排结合插入排序
比如使用快排之前添加上以下代码
if(l > r - 60){
在arr[l...r]插入排序
O(N^2) 小样本量的时候,跑的快
}
大样调度快排快,O(N*logN) 小样插入快O(N^2)
对于Array里面的sort排序,在基数时候用快排,在不是基数的时候用归并,原因在稳定性上,
因为基数的时候,稳定性没用(相同的基数谁在前后无所谓),快排时间复杂度低
无序表的用法
使用Hashset HashMap
有序表的用法
有序表的固定操作
1、void put(K key,V value):将一个(key,value)记录加入到表中,或者将key的记录更新成value
2、V get(K key):根据给定的key,查询value并返回
3、void remove(K key):移除key的记录。
4、boolean containsKey(K key):询问是否有关于key的记录
5、K firstKey():返回所有键值的排序结果中,最左(最小)的那个
6、K lastKey():返回所有键值的排序结果中,最右(最大)的那个
7、K floorKey(K key):如果表中存过key,返回key;否则返回所有键值的排序结果中,key的最后一个
8、K ceilingKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中,key的最后一个。
以上所有操作时间复杂度都是O(logN),N为有序表含有的记录数