排序算法(8)------计数排序

参考文章:https://blog.csdn.net/ns_code/article/details/20478753

前言

计数排序、基数排序和桶排序三种排序算法由于都不是基于比较的排序,因此这三种排序算法可以以线性时间运行。但是因为限制条件的特殊性,因此应用面没有基于元素比较的排序算法广,但是在很多特定的情况下还是蛮有用途的,而且效率极高。


计数排序

计数排序是建立在这样的前提条件下的:假设n个输入元素的每一个都是0到k区间内的一个整数,其中k为某个整数。因此我们后面所写的程序也只是针对0k之间的元素进行排序,换句话说,排序元素中不能有负数。

计数排序的基本思想是:对一个输入元素x,先确定所有输入元素中小于x的元素个数,那么排序后x所在的位置也就明确了。比如,所有的输入元素中有10个元素小于x,那么排好序后x的位置序号就应该是11。当然,如果有相同元素,自然要放到相邻的位置上。

算法导论上给出了计数排序的很详细的伪代码,我们根据此伪代码,并设数组arr为输入数组,arr中的每个元素值在0到k之间,brr为排序后的输出数组,crr记录arr中每个元素出现的次数。写出代码如下:

    //计数排序1:使用额外数组brr来保存排序结果
    /*
     * 计数排序的顺序是从小到大
     * arr[0...len-1]为排序数组,每个元素均是0~k中的一个数
     * brr[0...len-1]为排序后的输出数组
     * crr[0...k]保存arr数组中每个值出现的次数
     */
    public void countsort1(int arr[],int brr[],int crr[],int len,int k) {
        int i , j = 0;
        //统计数组arr中每个元素值出现的次数,用crr数组记录
        for(i=0;i<len;i++) {
            crr[arr[i]]++;
        }
        /* 求arr数组中小于等于i(这里的i是arr数组中出现的值)的元素个数,对于一个输入元素i,
         * 确定了所有输入元素中小于i的元素个数,那么排序后i所在的位置也就明确了
        */
        //显然如果arr中值的范围很大,那这里for循环就会遍历次数也会很大
        for(i = 1;i<=k;i++) {
            //同样这里也用crr数组记录
            crr[i] += crr[i-1];
        }
        //遍历arr数组,把arr中的元素放在brr中对应的位置上
        for(i = len-1;i>=0;i--) {

            /*假设arr[i]为1,crr[arr[i]]为9,并且1在arr数组中出现了3次,因为crr[arr[i]]=crr[arr[i]-1]+arr[i]值在arr中出现的次数
              arr[i]值有三个,则arr[i]值在brr中第一个下标为9-1=8,接着crr[arr[i]]--,则arr[i]对应值的下一个坐标为
              8-1=7,接着crr[arr[i]]又减1,最后一个arr[i]坐标就为7-1=6
            */

            brr[crr[arr[i]]-1] = arr[i];
            crr[arr[i]]--;
        }
    }

很明显上面代码的时间复杂度为O(n+k),但用到了brr来保存排序结果,我们可以它做些改进,使排序原地进行,如下:

//计数排序2:就地排序,不使用额外数组
    /*
        第二种形式实现计数排序
        计数排序后的顺序为从小到大
        arr[0...len-1]为待排数组,每个元素均是0-k中的一个值
        crr[0...k]保存0...k中每个值在数组arr中出现的次数
    */
    public void countsort2(int arr[],int crr[],int len,int k) {
        int i , j =0;
        //统计数组arr中每个元素出现的次数
        for(i = 0;i<len;i++) {
            crr[arr[i]]++;
        }
        //直接根据crr[i]的大小,将元素放入arr适当的位置
        for(i = 0;i<=k;i++) {
            //crr下标从0~k,相当与排好序,只需按下标顺序取去里面存的arr元素,因为下标记录的就是对应的arr值
            while((crr[i]--)>0){
                arr[j++] = i;
            }
        }
    }

采用如下代码测试:

    @Test
    public void test() {
        int arr[] = {8,1,2,5,3,5,7,5,10};
        int len = arr.length;
        int brr[] = new int[9];
        int k = 10;
        int crr[] = new int[k+1];
        //countsort1(arr,brr,crr,len,k);
        //System.out.println(Arrays.toString(brr));
        countsort2(arr,crr,len,k);
        System.out.println(Arrays.toString(arr));
    }

测试结果如下:
这里写图片描述
最后我们稍微总结下计数排序的特点:

1、不是基于比较的排序,因此可以达到线性排序时间;

2、采取空间换时间的思想,需要brr和crr等辅助空间,但是时间复杂度仅为O(n+k)

3、稳定性好,这也是计数排序最重要的一个特性。

在实际工作中,当k=O(n)时,我们一般才会采取计数排序,如果k很大,则不宜采取该算法,尤其在如下情形下:
待排序元素为:1、3、8、5、10000000,这样会造成很大的资源浪费。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值