【数据结构与算法】关于排序(下)

写在前面:今天主要讨论时间复杂度为O(n)的排序算法。

  • 计数排序
  • 桶排序
  • 基数排序

1.计数排序

算法分析:基于分布的排序要找到待排序数组中最大和最小的元素,辅助count数组,这里的意义是用来记录相对顺序下每个元素出现了多少次。

举个例子就能解释明白:
step1找最大最小是为了确定统计的区间。
nums={2,3,1,4,6,4,3,5,3,2},在step2计数完成后,count={1,2,3,2,1,1},它的意思是,应该放在第一个位置的元素出现了1次(也就是nums中的元素“1”),放在第二个位置的元素出现了2次(也就是nums中出现了两个2),以此类推。nums[i]-min表示nums[i]这个元素大小的相对位置。
step3是为了保证元素的相对顺序。

public int[] countSort(int[] arr){
        //step1:找到最大最小
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for(int i = 0; i < arr.length; i++){
            max = Math.max(max,arr[i]);
            min = Math.min(min,arr[i]);
        }
        //step2:计数
        int[] count = new int[max - min + 1];
        for(int i = 0; i < arr.length; i++){
            count[arr[i] - min]++;
        }
        //step3:累计
        for(int i = 0; i < count.length - 1; i++){
            count[i + 1] = count[i] + count[i + 1];
        }
        //step4:排序
        int[] sorted = new int[arr.length];
        for(int i = arr.length - 1; i >=0; i--){
            int index = arr[i] - min;
            sorted[count[index] - 1] = arr[i];
            count[index]--;
        }
        return sorted;
    }

如果数组大小为N,差值为M
时间复杂度:O(N+M)。step1,step2,step4的时间复杂度都是O(N),step3的复杂度是O(M)
空间复杂度:O(N + M)。统计数组count占用O(M),最后用来存储结果的数组O(N).

局限性:

1.当数列最大最小值差距过大时,并不适用计数排序。

比如给定20个随机整数,范围在0到1亿之间,这时候如果使用计数排序,需要创建长度1亿的数组。不但严重浪费空间,而且时间复杂度也随之升高。

2.当数列元素不是整数,并不适用计数排序。

如果数列中的元素都是小数,比如25.213,或是0.00000001这样子,则无法创建对应的统计数组。这样显然无法进行计数排序。

2.桶排序

当数列取值范围过大或者不是整数时,计数排序就不适用了,可以使用桶排序。

桶排序是一种线性时间复杂度的排序方法,需要桶来辅助排序。

下面的方法创建和待排序元素数量相同的桶,每个桶的取值区间为(max - min) / (bucketNum - 1);

定位数组中某个元素所在的桶:(arr[i] - min) * (bucketNum - 1) / (max - min ),是按比例计算的。

 public double[] bucketSort(double[] arr){
        //step1:找最大最小计算差值
        double max = arr[0];
        double min = arr[0];
        for(int i = 1; i < arr.length; i++){
            if(max < arr[i]) max = arr[i];
            if(min > arr[i]) min = arr[i];
        }
        double d = max - min;
        //step2:创建桶
        int bucketNum = arr.length;
        ArrayList<LinkedList<Double>> bucketList = new ArrayList<LinkedList<Double>>(bucketNum);
        for(int i = 0; i < bucketNum; i++){
            bucketList.add(new LinkedList<Double>());
        }
        //step3:元素放入桶中
        for(int i = 0; i < arr.length; i++){
            int index = (int)((arr[i] - min) * (bucketNum - 1) /d);
            bucketList.get(index).add(arr[i]);
        }
        //step4:对每个桶中的元素分别排序
        for(int i = 0; i < bucketList.size(); i++){
            Collections.sort(bucketList.get(i));
        }
        //step5:得到排序后的元素
        double[] sorted = new double[arr.length];
        int index = 0;
        for(List<Double> list : bucketList){
            for(double num : list){
                sorted[index++] = num;
            }
        }
        return sorted;
    }

复杂度分析:

假设原始数列有n个元素,分成m个桶(这里 m=n),平均每个桶的元素个数为n/m。
step1:求最大值和最小值,运算量为n。

step2:创建桶,运算量为m。

step3:将所有元素放入桶中,运算量为n。

step4:在每个桶内部做排序,由于使用了O(nlogn)的排序算法,所以运算量为 n/m * log(n/m ) *m。

step5:构造有序数组,运算量为n。

总的运算量为 3n+m+ n/m * log(n/m ) * m = 3n+m+n(logn-logm) 。

去掉系数,时间复杂度为:

O(n+m+n(logn-logm)) 

这里n = m,因此时间复杂度为O(n)

如果桶内元素分布情况不均衡,极端情况下所有元素都在一个桶内,会退化成O(nlogn)。

空间复杂度:

桶占用的空间O(m) + 待排序元素在桶中占用的空间O(n)

O(m+n)

这里n = m,因此空间复杂度为O(n)

 

3.基数排序

根据待排序元素每一位进行排序,有低位到高位以及从高位到低位的方法。

借助“分配”和“收集”两个操作对关键字的每一位进行排序

//基数排序
    public int[] radixSort(int[] arr,int d){
        int n = 1;//位数
        int len = arr.length;
        int[][] bucket = new int[10][len];//桶子数组
        int[] count = new int[10];//每一位有几个元素出现
        while(n < d){
            //step1:分配
            for(int i = 0; i < len; i++){
                int digit = (arr[i]/n) % 10;//当前排序位数上是几
                bucket[digit][count[digit]] = arr[i];
                count[digit]++;
            }
            //step2:收集
            int k = 0;
            for(int i = 0; i < 10; i++){
                if(count[i] != 0){
                    for(int j = 0; j < count[i];j++){
                        arr[k++] = bucket[i][j];
                    }
                }
                count[i] = 0;
            }
            k = 0;
            n *= 10;
        }
        return arr;
        
    }

空间复杂度:用了一个二维数组10*n,复杂度为O(n)

时间复杂度:假设位数为s,r为基数(10进制r=10),基数排序需要s趟分配和收集,一趟分配需要O(n),一趟收集需要O(r),最多O(n),所以时间复杂度为O(s(n+r))与序列初始状态无关

稳定性:稳定。按位排序是稳定的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值