计数排序与稳定排序

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

        先来说一下实现的原理,假设有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;
}

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

猜你喜欢

转载自www.cnblogs.com/SysoCjs/p/9775669.html