【排序】——计数排序

【排序】——计数排序

1、应用场景:

计数排序可以看作是一种特殊的桶排序。

当要排序的n个数据,所处范围并不大,我们就可以把数据划分成n个桶。每个桶内的数据值都是相同的,桶内不需要再排序。

比如高考的分数系统 满分900分,最低0分。因此数据范围只有0-900,但是考生却很多有几十万。此时我们要对这几十万个考生的分数进行排名,就可以使用到计数排序。

2、一个简化的例子说明:

1、假设只有8个考生,分数在0到5之间,我们把这8个考生的分数放到一个数组里面,记为分数数组arr:

int[] arr = {2,5,3,0,2,3,0,3};

在这里插入图片描述

2、考生的成绩从0到5分,用一个计数数组countArr[6]来表示桶,这个数组的下标对应分数0到5。数组的每个元素的值存的是对应下标表示的分数有多少个考生。此时数组的值应该是这样的。

int[] countArr = new int[6];  //对应下标为考试分数
countArr[0] = 2; //考试分数为0的考生有2个
countArr[1] = 0; //考试分数为0的考生有0个
countArr[2] = 2; //考试分数为2的考生有2个
countArr[3] = 3; //考试分数为3的考生有3个
countArr[4] = 0; //考试分数为4的考生有0个
countArr[5] = 1; //考试分数为5的考生有1个

在这里插入图片描述

3、计数数组有了,那么我们还需要一个数组去存排序后的分数,记这个临时数组为tempArr。那么此时,我们怎么计算出分数在这个临时数组的存储位置呢? 从计数数组中我们可以看到,分数为3的考生有3个,分数小于3的考生有4个,那么分数小于3的考生的存储位置就为:tempArr[0],tempArr[1],tempArr[2],tempArr[3]。而分数为3的考生存储的位置就为:tempArr[4],tempArr[5],tempArr[6]。

4、有了这个思路,我们可以把计数数组countArr的每个位置上的值累加,这样就会得到一个全新的计数数组:

在这里插入图片描述

这个全新的计数数组中每个元素的值就可以表示为分数(计数数组下标)在临时数组tempArr中的最大存储位置+1。

在这里插入图片描述

从分数数组arr最后的一个元素开始遍历(为了保证排序算法稳定性)。

第1趟,找到3,在countArr的下标为3的元素值为7,于是我们把3存储到tempArr中下标为6(7-1)的位置,

然后countArr下标为3的元素值减一。

第2趟,找到0,在countArr的下标为0的元素值为2,于是我们把0存储到tempArr中下标为1(2-1)的位置,

然后countArr下标为0的元素值减一。

第3趟,找到3,在countArr的下标为3的元素值为6,于是我们把3存储到tempArr中下标为5(6-1)的位置,

然后countArr下标为0的元素值减一。

以此类推…

3、实现代码

/**
 * 思路:
 *  1、一个计数数组countArr
 *  2、一个临时数组存放排序后的数据tempArr
 *
 * @param arr
 */
private static void jishuSort(int[] arr) {
    int arrlength = arr.length;

    if (arrlength < 2) {
        return;
    }

    /*计算数组的最大值*/
    int max = arr[0];
    for (int i = 1; i < arrlength; i++) {
        if (max < arr[i]) {
            max = arr[i];
        }
    }

    /*一个计数数组countArr*/
    int[] countArr = new int[max + 1];

    /*计算每个元素的个数,放入计数数组中*/
    for (int i = 0; i < arrlength; i++) {
        countArr[arr[i]]++;
    }

    /*依次累加*/
    for (int i = 1; i <= max; i++) {
        countArr[i] = countArr[i - 1] + countArr[i];
    }

    /*一个临时数组存放排序后的数据tempArr*/
    int[] tempArr = new int[arrlength];

    /*把排序的数据存入临时数组*/
    for (int i = arrlength - 1; i >= 0; i--) {
        int tempIndex = countArr[arr[i]] - 1;
        tempArr[tempIndex] = arr[i];
        countArr[arr[i]]--;
    }

    /*将临时数组的结果返回给原数组*/
    for (int i = 0; i < arrlength; i++) {
        arr[i] = tempArr[i];
    }
}

4、代码优化

思路:上面的代码计数数组中下标为0之类很小的下标有可能会浪费掉。

比如一组数据{671,673,672,667,667,674,676},其中最大值为676,按照上面的代码,我们需要创建一个长度为677的计数数组,但是我们可以发现,它前面的[667]的空间完全浪费了,那怎样优化呢?

/**
 * 计数排序优化 : 采用偏移量来处理,防止空间浪费
 * @param arr
 */
private static void jishuSortPlus(int[] arr) {
    int arrLength = arr.length;

    if (arrLength < 2) {
        return;
    }

    int min = arr[0];
    int max = arr[0];
    for (int i = 0; i < arrLength; i++) {
        min  = Math.min(min, arr[i]);
        max = Math.max(max, arr[i]);
    }
    
    /*计数数组*/
    int[] countArr = new int[max - min + 1];

    /*计算每个元素的个数,放入计数数组中*/
    for (int i = 0; i < arrLength; i++) {
        countArr[arr[i] - min]++;
    }
    
    /*依次累加*/
    for (int i = 1; i < max - min + 1; i++) {
        countArr[i] = countArr[i - 1] + countArr[i];
    }

    /*一个临时数组存放排序后的数据tempArr*/
    int[] tempArr = new int[arrLength];

    for (int i = arrLength - 1; i >= 0; i--) {
        int tempIndex = countArr[arr[i] - min] - 1;
        tempArr[tempIndex] = arr[i];
        countArr[arr[i] - min]--;
    }

    /*把临时数组的值返回给旧数组*/
    for (int i = 0; i < arrLength; i++) {
        arr[i] = tempArr[i];
    }

}

参考:

1、极客时间学习网站课程

2、一文弄懂计数排序算法!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值