0
点赞
收藏
分享

微信扫一扫

排序算法之计数排序的优化

一:概述

在相关文章的前面已经说过了线性时间排序和计数排序的初识,在这里将说明计数排序的优化。

二:具体说明

<1>问题描述

从实现功能上来讲,前面的代码没有问题,但是只以数列的最大值来作为统计数组长度的依据,这并不严谨,例如数列:80,87,90,99,87,88,96.这个数列的最大值是99,但是最小值是80.如果创建长度为100的数组,那么前面的0到79都浪费掉空间了。

     <2>思路分析

其实很简单,不要以之前的输入数列的最大值+1作为数组的长度,而是以数列的最大值-最小值+1作为数组的长度。

同时以数列的最小值作为一个偏移量,用于计算统计在数组中的下标。以上面说的数组为例,数组的长度为99-80+1=20,偏移量等于数列的最小值80.对于第一个整数80,对应的统计下标为0,第二个整数的统计下标为7.

   <3>具体说明

这里以数列:95、94、91、98、99、90、99、93、91、92.

                  排序算法之计数排序的优化_数组

没有优化的计数排序只是简单的按照统计数组的下标输出元素,并没有真正的给原始数组进行排序。如果只是单纯的给整数排序,这样做并没有问题。但是如果在实现的业务里面,例如给学生的成绩进行排序,遇到相同的分数就会分不清是谁了。

来看看下面这个例子:

                  排序算法之计数排序的优化_最小值_02

给出一个学生的学生成绩表,要求按成绩从低到高进行排序,如果成绩相同,则遵循原表的固有顺序。,在统计的时候,只知道两个成绩为95的学生成绩并列,却区分不出来哪一个是王麻子哪一个是白虎。

                  排序算法之计数排序的优化_数组_03

在这种情况下,需要稍微的改变之前的逻辑,再填充完统计数组之后,对统计数组进行变形。仍然以上面的学生成绩表为例。将之前的统计数组变为下面的样子。

                  排序算法之计数排序的优化_数组_04

如何变形呢?其实就是从统计数组的第二个元素开始,每一个元素都加上前面的所有元素之和。相加的目的是让统计数组存储的元素值等于相应的整数的最终排序位置的序号。例如一个下标是9的元素是5,代表原始数列的整数9,最终排序在第5位。

接下来创建输出数组sortedArray,数组长度和输入数列一致。然后从后向前遍历输入数列。

第一步:遍历学生成绩的最后一行白虎的成绩。

白虎的成绩是95分,找到countArray下标是5的元素,值是4,代表白虎的成绩排名位置在第4位。

同时给countArray下标是5的元素值减1,从4变为3,代表下次再遇到95的成绩时,最终排名为3.

                  排序算法之计数排序的优化_计数排序_05

第2步,遍历成绩表倒数第2行的青龙同学的成绩。

青龙同学的成绩为94分,找到countArray下标是4的元素,值是2,代表青龙的成绩排名位置在第2位。


同时,给countArray下标是4的元素值减1,从2变为1,代表下次再遇到94分的成绩时(实际上已经遇不到了),最终排名第1.

                  排序算法之计数排序的优化_最小值_06

第3步,遍历成绩表倒数第3行的王麻子同学的成绩。

王麻子的成绩是95分,找到countArray下标是5的元素,值是3(最初是4,减1变成了3),代表小红的成绩排名位置在第3位。

同时,给countArray下标是5的元素值减1,从3变成2,代表下次再遇到95分的成绩时(实际上已经遇不到了),最终排名是第2.

                  排序算法之计数排序的优化_计数排序_07


这样,同样95分的王麻子和白虎就能够清楚地排出顺序了。因此,优化版本的计数排序属于稳定排序

后面的遍历过程以此类推。

<4>代码实现

// 计数排序的优化
public class Sort8 {

public static int[] countSort(int[] array) {

    // 1. 得到数列的最大值和最小值,并算出插值d
    int max = array[0];
    int min = array[0];
    for(int i = 1; i < array.length; i++) {
        if(array[i] > max) {
            max = array[i];
        }
        if(array[i] < min) {
            min = array[i];
        }
    }
    int d = max - min;

    // 2.创建统计数组并统计对应的元素的个数
    int[] countArray = new int[d + 1];
    for(int i = 0; i < array.length; i++) {
        countArray[array[i] - min]++;
    }


     // 3. 统计数组做变形,后面的元素等于前面元素之和
  for(int i = 1; i < countArray.length;i++) {

      countArray[i] += countArray[i - 1];
  }

    // 4. 倒序遍历原始数列,从统计数组找到正确的位置,输出结果数组
    int[] sortedArray = new int[array.length];
    for(int i = array.length - 1;i > 0;i--) {
        sortedArray[countArray[array[i]- min] - 1] = array[i];
        countArray[array[i] - min]--;
    }
    return sortedArray;
}



    public static void main(String[] args) {
        int[] array = new int[]{95, 96, 91, 99, 80, 91, 92, 99, 100};
       int[] sortedArray = countSort(array);
       System.out.println(Arrays.toString(array));

    }
}

<5>说明解释

如果原始数列的规模是n,最大和最小整数的差值是m,代码的第1、2、4步都涉及遍历原始数列,运算量都是n,第3步遍历统计数列,运算量是m,所以总体运算量是3n+m,去掉系数,时间复杂度是O(n+m).

至于空间复杂度,如果不考虑结果数组,只考虑统计数组大小的话,空间复杂度是O(m)。

注意:

  • 当数列最大和最小值差距过大时,并不适合用计数排序。                                                                                                         例如给出20个随机整数,范围在0到1亿之间,这时如果使用计数排序,需要创建长度为1亿的数组,不但严重浪费空间,而且时间复杂度也会随之升高。
  • 当数列元素不是整数时,也不适合用计数排序。                                                                                                                     如果数列中的元素都是小数,如26.152,或者0000000001这样的数字,则无法创建对应的统计数组。这样显然无法进行计数排序。


































举报

相关推荐

0 条评论