常用排序算法之非比较算法

这篇文章中我们来探讨一下常用的非比较排序算法:
  • 计数排序
  • 基数排序
  • 桶排序

在一定的条件下,它们的时间复杂度可以达到O(n).

1、计数排序(Counting Sort)

基数排序用到一个额外的计数数组C,根据数组C来将原数组A中的元素排到正确的位置。
通俗的来讲,例如有10个年龄不同的人,假如统计出有8个人的年龄不比小明大(即小于等于小明的年龄,这里也包括小明),那么小明的年龄就排在第8位,通过这种思想可以确定每个人特殊处理(保证稳定性);通过反向填充目标数组,填充完毕后将对应的数字统计递减,可以确保计数排序的稳定性。
计数排序的步骤如下:
1、统计数组A中每个值A[i]出现的次数,存入C[A[i]];
2、从前向后,使数组C中的每个值等于其与前一项相加,这样数据C[A[i]]就变成了代表数组A中小于等于A[i]的元素个数;
3、反向填充目标数组B:将数组元素A[i]放在数组B的第C[A[i]]个位置(下标为C[A[i]]-1),每放一个元素就将C[A[i]]递减。
代码实现如下:

public static void sortCounting(int[] A){
        //C数组应该以A中最大的值为数组大小。这个值就是基数假设为100
        int[] C = new int[100];
        for (int i : C) {
            C[i]= 0;
        }
        //为每个元素计数
        for (int i = 0;i<A.length;i++){
            C[A[i]]++;
        }
        //把C中每个位置都是前面的计数之和。
        for (int i = 1;i<C.length;i++) {
            C[i] = C[i] + C[i - 1];
        }
        //分配临时空间b ,做暂存数据
        int[] B = new int[A.length];
        //把A的每个元素放到按照位置学定理放到B上
        for (int i = A.length - 1; i >= 0; i--) {
            B[--C[A[i]]] = A[i];
        }
        for (int i = 0;i<A.length;i++) {
            A[i] = B[i];
        }
 }

计数排序的时间复杂度和空间复杂度与数组A的数据范围(A的最大值与最小值的差加上1)有关,因此对于数据范围很大的数组,计数排序需要大量的时间和内存。

2、基数排序(Radix Sort)

基数排序的发明可以追溯带1887年赫尔曼何乐礼在打孔卡片制表机上的贡献。
它是这样实现的:
将所有待比较正整数统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始进行基数为10的计数排序,一直到最高位计数排序完后,数列就变成一个有序序列(利用了计数排序的稳定性)。
Java代码如下:

 /**
     * 基数排序
     *
     * @param A  待排序数组
     * @param dn 待排序数组中的最大位数
     */
    public static void sortRadix(int[] A, int dn) {

        //基数为10
        int k = 10;
        //保留每个序列的值,个位、十位。。。
        int[] A1 = new int[A.length];
        int[] C = new int[k];
        for (int j = 1; j <= dn; j++) {
            //每次归零
            for (int i = 0; i < k; i++) {
                C[i] = 0;
            }
            for (int i = 0; i < A.length; i++) {
                int radix[] = {1, 1, 10, 100, 1000, 10000, 100000};
                A1[i] = A[i] / radix[j] % 10;
            }
            //不能单纯的调用,因为这里要保留A[i]中原来的数据
            //sortByCount(C);

            //为每个元素计数
            for (int i = 0; i < A.length; i++) {
                C[A1[i]]++;
            }
            //把C中每个位置都是前面的计数之和。
            for (int i = 1; i < C.length; i++) {
                C[i] = C[i] + C[i - 1];
            }
            //分配临时空间b ,做暂存数据
            int[] B = new int[A.length];
            //把A的每个元素放到
            for (int i = A.length - 1; i >= 0; i--) {
                B[--C[A1[i]]] = A[i];
            }
            for (int i = 0; i < A.length; i++) {
                A[i] = B[i];
            }
        }
    }

基数排序的时间复杂度是O(n*dn),其中n是待排序元素个数,dn是数字位数。这个时间复杂度不一定优于O(n log n),dn的大小取决于数字位的选择(比如比特位数),和待排序数据所属数据类型的全集的大小,dn决定了进行多少轮处理,而n是每轮处理的操作数目。
如果考虑和比较排序进行对照,基数排序的形式复杂度虽然不一定更小,但由于不进行比较,因此其基本操作的代价较小,而且如果适当的选择基数,dn一般不大于log n,所以基数排序一般要快过基于比较的排序,比如快速排序。由于证书也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序并不是只能用于整数排序。

3、桶排序(Bucket Sort)

桶排序也叫箱排序。工作的原理是将数组元素映射到有限数量个桶里,利用计数排序可以定位桶的边界,每个桶再各自进行桶内排序(使用其它排序算法或递归方式继续使用桶排序)
桶排序的实现代码如下:

    /**
     * 桶排序
     */
    public static void sortBucket(int[] A) {

        //设置桶的个数,根据要排序的数组中的值合理设置这个值,这决定了map桶的映射;
        //这里假设A为[0,39]之间,则以10分割,分割为四个桶。
        int bn = 4;
        //桶里存放的信息
        int C[] = new int[bn];

        for (int i = 0; i < A.length; i++) {
            C[A[i] / 10]++;
        }
        for (int i = 1; i < bn; i++) {
            C[i] = C[i] + C[i - 1];
        }
        int[] B = new int[A.length];

        for (int i = A.length - 1; i >= 0; i--) {
            B[--C[A[i] / 10]] = A[i];
        }
        for (int i = 0; i < A.length; i++) {
            A[i] = B[i];
        }
        //对每个桶中的数进行插入排序
        for (int i = 0; i < bn; i++) {
            int left = C[i];
            int right = (i == bn - 1 ? A.length - 1 : C[i + 1] - 1);
            if (left < right) {
                insertSort(A, left, right);
            }
        }


    }

    /**
     * 插入排序
     *
     * @param A
     * @param left
     * @param right
     */
    private static void insertSort(int[] A, int left, int right) {

        for (int i = left + 1; i <= right; i++) {
            int get = A[i];
            int j = i - 1;
            while (j >= left && A[j] > get) {
                A[j + 1] = A[j];
                j--;
            }
            A[j + 1] = get;
        }


    }

桶排序不是比较排序,不受到O(n log n)下限的影响,它是鸽巢排序的一种归纳结果,当所要排序的数组分散均匀的时候,桶排序拥有线性的时间复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值