线性时间的排序之计数排序
回顾之前的排序:
冒泡排序:在一个数组中,如果左边的大于右边的,则它两互相交换位置。
堆排序:同样也是交换元素的位置
有一些特殊得排序并不基于元素比较,如计数排序、桶排序、基数排序。。
一、初始计数排序
假设数组中有20个随机数,取值范围为0~10,要求用最快得速度把这20 个整数从小到大进行排序。
根据这个有限的范围建立一个长度为11的数组,数组下标从0开始。元素初始值为0.
假设值为:
9,3,5,4,9,1,2,7,8,1,3,6,5,3,4,0,10,9,7,9
对这个无序的随机数列进行遍历,每一个整数按照其值对号入座,同时,对应数组下标的元素进行加1操作。
最终根据其值的大小,对应数组中的下标位置,并加1.。其意思就是,给了一个长度为11的数组。每一个初始都是0.然后遍历数组时根据值的大小,对应在数组中找到位置,并加一次(出现了一次就加1,依此类推,出现几次就在对应的位置加几次。)
则最终得到下面的数组
1 ,2,1,3,2,2,1,2,1,4,1
0,1,2,3,4,5,6,7,8,9,10
第一排是对应下面的值出现过的次数。这样看就明白了。
此时,有了这个统计结果。我们就遍历整个数字组。输出数组元素的下标值,元素值是几,就输出几次。
比如:0出现了1次,第一个就是0,1在第一排显示的是2,则就遍历两次,就是出现了两次1,以此类推:
0,1,1,2,3,3,3,4,4,5,5,6,7,7,8,9,9,9,9,10
看,这样是不是就给排序了。。亲手写写还是很容易懂得,光看真的不如动手一画。
此时输出的数列还是有序的。它适用于一定范围内的整数排序,在值不是很大的情况下性能还是很快的。
代码:
public class quikSort {
public static int[] countSort(int[] array){
//1,得到数列的最大值
int max =array[0];
for (int i = 1; i <array.length ; i++) {
if (array[i]>max){
max=array[i];
}
}
//2,根据得到的最大值确定统计数组的长度
int[] countArray = new int [max+1];
//3,遍历梳理,填充统计数组
for (int i = 0; i <array.length; i++) {
countArray[array[i]]++;
}
//4,遍历统计数组,输出结果
int index = 0;
int[] sortedArray = new int[array.length];
for (int i = 0; i <countArray.length ; i++) {
for (int j = 0; j <countArray[i] ; j++) {
sortedArray[index++]=i;
}
}
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[] sortedArray =countSort(array);
System.out.println(Arrays.toString(sortedArray));
}
}
结果:
代码开头求数列的最大整数值max.后面创建的统计数组countArray,长度是max+1,以此保证数组的最后一个下标是max.
二,计数排序的优化
以上只以数列的最大值来决定统计数组的长度并不严谨。万一值的大小并不是从0开始,从80开始到99,那么创建一个数组长度为100 的数组,80以前的空间岂不是被浪费了。
解决办法;
不以输入数列的最大值+1作为统计数组的长度,而是以数列最大值-最小值+1作为统计数组的长度。
同时,数列的最小值作为一个偏移量,用于计算整数在统计数组中的下标。
以刚才的数组为例,统计数组的长度为99-90+1=10,偏移量等于数列的最小值80.
对于第一个整数,对应得统计数组下标就是(假设第一个是95),则下标就是95-90=5,则在第5个位置。
上述方法只是给一个整数进行了排序并且没有浪费空间,但是用到实际项目应用中,还是有以写缺陷,假设对一个小学期末成绩做大小排序,出现了两个一摸一样的分数,99.但是这样排序的话并不是到底是小明,还是小张。此时又改怎么解决呢?
简要说明:就是从统计数组的第 2 给 元素开始,每一个元素都加上前面所以元素之和。这样加的目的是让统计数组存储的元素值,等于相应整数的最终排序位置的序号,例如下标是9的元素值为5,代表原始数列的整数9,最终排序在第5位。
接下来创建一个新的数组,长度和输入数列一致,然后从后向前遍历输入数列。
代码说明:
public class quikSort2 {
public static int[] countSort(int[] array){
//1,得到数列的最大值
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,94,91,98,99,90,99,93,91,92};
int[] sortedArray =countSort(array);
System.out.println(Arrays.toString(sortedArray));
}
}
结果:
优化后的计数排序属于稳定排序,但是也有它的局限性。
1,当数列最大和最小值差距过大时,并不适合用计数排序。
例如给出20个随机整数,范围在0到一亿之间,这时如果用计数排序,需要创建的长度过大,严重浪费空间。时间复杂度也会随之升高。
2,当数列元素不是整数时,也不适合用计数排序。
比如带小数点的,也无法排序。
额。。。。。消化消化再继续。。