数据结构---计数排序

Source

冒泡排序,还是快速排序,都是基于元素之间的比较来进行排序
有一些特殊的排序并不基于元素比较,如计数排序、桶排序、基数排序。

计数排序:利用数组下标来确定元素的正确位置的

线性时间排序算法:

  1. 计数排序
  2. 桶排序

计数排序

在这里插入图片描述
遍历数组,记录每个数字出现的次数
在这里插入图片描述
该数组中每一个下标位置的值代表数列中对应整数出现的次数。

最后:直接遍历数组,输出数组元素的下标值,元素的值是几,就输出几次。
在这里插入图片描述

适用范围:它适用于一定范围内的整数排序。在取值范围不是很大的情况下,

JAVA实现

package mysort.countingSort;

import java.util.Arrays;

public class countingSort {
    
      
    public static int[] countSort(int[] array){
    
      
        //取数列的最大值
        int max = array[0];
        for (int i = 1;i<array.length;i++){
    
      
            if(array[i]>max){
    
      
                max = array[i];
            }
        }
        //根据数组最大值确定统计数组countArray的长度
        //默认条件是:数组元素非负
        int[] countArray = new int[max+1];
        //遍历数组,统计个数,并填充统计数组countArray
        for (int i=0;i<array.length;i++){
    
      
            countArray[array[i]]++;
        }

        //遍历统计数组countArray,输出结果
        //index是记录sortedArray数组填充到哪的一个下标(每次填充,就加1)
        int index = 0;
        //创建一个和array一样长度的数组,用于存放最后的输出结果
        int[] sortedArray = new int[array.length];
        //外层循环是对countArray的每一个位置进行遍历
        //内层循环是针对countArray的一个位置,
        // 这个位置值是i
        // 对应的元素个数是countArray[i]
        //把他存入sortedArray
        for (int i=0;i<countArray.length;i++){
    
      
            for (int j=0;j<countArray[i];j++){
    
      
                sortedArray[index]=i;
                index++;
            }
        }
        return sortedArray;
    }

    public static void main(String[] args) {
    
      
        int [] array = new int[]{
    
      4,4,6,5,3,2,8,1,7,5,6,0,10};
        int[] sortarrray = countSort(array);
        System.out.println(Arrays.toString(sortarrray));
    }
}

在这里插入图片描述
在这里插入图片描述

计数排序优化

存在的问题1:

我们只以数列的最大值来决定统计数组的长度,其实并不严谨。
解决方案:只要不再以输入数列的最大值+1作为统计数组的长度,而是以数列最大值-最小值+1作为统计数组的长度即可。
数列的最小值作为一个偏移量,用于计算整数在统计数组中的下标。

在这里插入图片描述
数组的长度为99-90+1=10
第1个整数95,对应的统计数组下标是95-90 = 5

存在的问题2

给出一个学生成绩表,要求按成绩从低到高进行排序,如果成绩相同,则遵循原表固有顺序。
注意:这里的重点条件是:如果成绩相同,则遵循原表固有顺序。
在这里插入图片描述
在这里插入图片描述
将统计数组变形为:
在这里插入图片描述
变形规则是:从统计数组的第2个元素开始,每一个元素都加上前面所有元素之和。
这样相加的目的,是让统计数组存储的元素值,等于相应整数的最终排序位置的序号。

第1步,遍历成绩表最后一行的小绿同学的成绩。
小绿的成绩是95分,找到countArray下标是5的元素,值是4,代表小绿的成绩排名位置在第4位。
同时,给countArray下标是5的元素值减1,从4变成3,代表下次再遇到95分的成绩时,最终排名是第3。(小红就是第三位)
这样就达到了:如果成绩相同,则遵循原表固有顺序的目的。
在这里插入图片描述

这样一来,同样是95分的小红和小绿就能够清楚地排出顺序了,也正因为此,优化版本的计数排序属于稳定排序。

JAVA实现

package mysort.countingSort;

import java.util.Arrays;

public class countingSort2 {
    
      
    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];
        //3统计对应元素的个数 (array[i]-min就是array数组中元素的偏移地址)
        for (int i=0;i<array.length;i++){
    
      
            countArray[array[i]-min]++;
        }
        System.out.println("原始的计数数组为"+Arrays.toString(countArray));
        //统计数组做变形,后面的元素等于前面的元素之和
        for (int i =1;i<countArray.length;i++){
    
      
            countArray[i]+=countArray[i-1];
        }
        System.out.println("变形后的计数数组为"+Arrays.toString(countArray));
        //4.倒序遍历原始数列,从统计数组找到正确位置,输出到结果数组
        //countArray[array[i]-min]-1其中减去1的原因是在sortedArray数组中,下标从0开始。。
        //就以95小绿为例子
        int[]sortedArray = new int[array.length];
        for (int i=array.length-1;i>=0;i--){
    
      
            sortedArray[countArray[array[i]-min]-1]=array[i];
            //在将countArray中存储的数字(位置)减去1
            countArray[array[i]-min]--;
        }
        return sortedArray;
    }

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

在这里插入图片描述

在这里插入图片描述

时间复杂度:O(n+m)。代码第1、2、4步都涉及遍历原始数列,运算量都是n,第3步遍历统计数列,运算量是m,所以总体运算量是3n+m ,用大O表示法得去掉系数。

空间复杂度:只考虑统计数组大小的话,空间复杂度是O(m)。

局限性

  1. 当数列最大和最小值差距过大时,并不适合用计数排序。
  2. 当数列元素不是整数时,也不适合用计数排序。

解决方案:桶排序