不基于比较的排序(桶排序)和算法的稳定性

桶排序思想下的排序

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为有序表含有的记录数

posted @ 2022-01-19 20:20  刘小呆  阅读(250)  评论(0编辑  收藏  举报