zoukankan      html  css  js  c++  java
  • 计数排序与稳定排序

    我们常用的排序算法,有冒泡算法、快速算法等,它们都是基于元素之间的比较来进行排序,有一种特殊的算法不是基于元素比较,而是利用数组下标来确定元素在数组的位置,这种算法就是“计数排序”。

            先来说一下实现的原理,假设有20个随机整数的数组array,他们值分别是:9,3,5,4,9,1,2,7,8,1,3,6,5,3,4,0,10,9,7,9.有没有发现这些整数的大小都是在[0,10]区间内,这时我们可以定义一个长度为11的数组countArray,每个元素的初始值都为0.当我遍历这20个随机整数时,每个值在countArray的下标位置对号入座。

    注意:不是赋值,而是每有一个值,就在对应位置+1,譬如遍历array的第一位元素9,就会在array[9]位置+1,遍历array的第二位元素3时,就在countArray[3]位置+1,如此类推。

    最终遍历整个array数组后,countArray数组变成以下情况:

    value: 1 2 1 3 2 2 1 2 1 4 1
    index: 0 1 2 3 4 5 6 7 8 9 10

    每一个value值对应表示下标值出现的次数,有了这个统计结果,那么重新排序就显得简单粗暴。

    直接依次输出排序后的数组结果:0,1,1,2,3,3,3,4,4,5,5,6,7,7,8,9,9,9,9,10

    下面是代码实现:

    public int[] countSort(int[] array){
        //获取array数组中元素的最大值
        int max = array[0];
        for(int i=1; i<array.length; i++){
            if(array[i]>max){
                max = array[i];
            }
        }
    
        //根据数组的最大值确定统计数组的长度
        int[] countArray = new int[max+1];
        
        //遍历array,填充countArray
        for(int i=0; i<array.length; i++){
            countArray[array[i]]++;
        }
    
        //遍历统计数组,输出结果
        int index = 0;
        int[] sortArray = new int[array.length];
        for(int i=0; i<countArray.length; i++){
            for(int j=0; j<countArray[i]; j++){
                sortArray[index++] = i;
            }
        }
        
        return sortArray;
    }

    好了,代码已经给出来了,必须注意一点是上面代码有不少局限性,首先获取array数组的最大值+1作为countArray数组的长度,不严谨,其次,这是在数组元素值为[0,10]区间的整数才可以。

    下面给出改进栗子:

    原始无序数组array:

    value 95 94 91 98 99 90 99 93 91 92
    index 0 1 2 3 4 5 6 7 8 9

    对于统计数组countArray,我们采用array数组的(最大值-最小值+1)作为countArray的长度,即(99-90+1)=10,用array数组的最小值90作为countArray数组的偏移量,譬如95,则(95-90)=5,会在countArray[5]的值加1,对于纯碎的整数排序,只要在上面代码稍微修改一下就ok了,但是对于现实业务排序,就显得很鸡肋,譬如学生的成绩排序,

    姓名成绩
    Sam 90
    Tom 99
    Jack 95
    Jane 94
    Kan 95

    按照原来的统计思路,可以得到下面的统计数组:

    value 1 0 0 0 1 2 0 0 0 1
    index 0 1 2 3 4 5 6 7 8 9

    这种情况下,如果成绩相同,应当按照固有顺序排序,但是,怎么确定原来的固有顺序对应的人呢?现在就要对统计数组进行变变形,统计数组从第二个元素开始,每个元素的值都加上前面所有元素的值的和。

    value 1 1 1 1 2 4 4 4 4 5
    index 0 1 2 3 4 5 6 7 8 9

    为什么要相加呢?目的是存储当前元素值的最后位置,譬如下标为9的元素,不管有多少个值为99的元素,反正该值的元素最后一个位置是5.

    倒序遍历array数组,第一个被遍历的元素Kan的95,找到countArray数组下标为5的value值,是4,代表Kan是在输出数组sortArray中排的位置是第四位(即,是输出数组sortArray[3]的值,注意,这个下标值可以通过countArray对应的元素值减1得到,countArray对应的元素值又可以通过countArray[array[i]-min]得到),然后countArray数组下标为5的value值相应减去1,4—>3,依次遍历,过程中再遇到95,那么该95排的位置是第三位,如此类推。这样一来,就可以清楚的将同样是95分的Jack和Kan排出顺序来,这就是稳定排序

    代码实现:

    public int[] countSort(int[] array){
        //得到array数组的最大值和最小值,以便计算得到countArray的长度
        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[] countArray = new int[max-min+1];
        //统计对应元素的个数
        for(int i=0; i<array.length; i++){
            countArray[array[i]-min]++;
        }
    
        //对统计数组进行变形
        int sum = 0;
        for(int i=0; i<countArray.length; i++){
            sum += countArray[i];
            countArray[i] = sum;
        }
    
        //倒序遍历array数组,确定各元素的固有位置
        int[] sortArray = new int[array.length];
        for(int i=array.length; i>0; i--){
            sortArray[countArray[array[i]-min]-1] = array[i];//这里的sortArray下标值就是上面高亮标记提到过的
            countArray[array[i]-min]--;
        }
        return sortArray;
    }

            至此,计数排序和稳定排序结束,稳定排序就是在计数排序的基础上进化的,另外还要注意它们的局限性,原始数组元素的值必须都是整数,还有当数组的最大值与最小值差距较大时,使用计数排序显得没有意义了,因为计数排序是以牺牲空间复杂度来换取时间复杂度的算法。

  • 相关阅读:
    MySQL-简述
    APP测试-Solo Pi工具-性能测试
    APP测试-弱网测试
    iOS 认识runtime 中的三个指针 isa , IMP , SEL
    iOS 什么是函数式编程
    iOS 根据农历日期 获取当前的农历年份 即 干支纪年法算农历年
    iOS 当公司有人向你提问,你该如何应对?
    Mac 环境 下使用Charles 抓包Http/Https请求
    iOS iPhone X 适配启动图片
    iOS11 仿大标题 导航栏
  • 原文地址:https://www.cnblogs.com/SysoCjs/p/9775669.html
Copyright © 2011-2022 走看看