计数排序(Count Sort)
基本思想
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
一句话:用辅助数组对数组中出现的数字计数,元素转下标,下标转元素。
算法描述
①找出待排序的数组中最大和最小的元素;
②统计数组中每个值为i的元素出现的次数,存入数组helper的第i项;
③利用偏差解决数组中可能有负数的问题;
④对所有的计数累加(从helper中的第一个元素开始,每一项和前一项相加);
⑤反向填充目标数组:将每个元素i放在新数组的第helper[i]项,每放一个元素就将helper[i]减去1。
图例演示
代码实现
/**
* 计数排序
* 问题:数组有负数,不能解决
*/
public static int[] sort ( int[] arr ) {
if (arr.length < 2) {
return arr;
}
//创建辅助数组,数组大小为原数组的最大值
int[] helper = new int[maxOf(arr)];
for (int e : arr) {
//将原数组的元素作为辅助数组的下标,出现一次次数+1
helper[e]++;
}
//数据回填的位置
int current = 0;
//遍历辅助数组
for (int i = 0; i < helper.length; i++) {
//当前元素出现次数大于1,依次赋给原数组
while (helper[i] > 0) {
//每次赋值后,指针右移
arr[current++] = i;
helper[i]--;
}
}
return arr;
}
/**
* 求数组中的最大值
*/
public static int maxOf ( int[] arr ) {
int max = 0;
for (int i = 0; i < arr.length; i++) {
if (arr[i] > arr[max]) {
max = i;
}
}
return arr[max];
}
这种方法,虽然可以解决数组中有重复性元素的问题,但是并不能解决数组中出现负数的问题。
改进思路
利用偏差思想,原数组转辅助数组下标时,原数组元素-数组最小值(计算偏差,无论数组最小值的正负,偏差最小值为0);下标回填数组时,辅助数组索引+最小值(加上偏差),即可解决负数问题。
最终代码
public static int[] sort ( int[] arr ) {
if (arr.length < 2) {
return arr;
}
int min = arr[0], max = arr[0];
//找出最小值和最大值
for (int i = 0; i < arr.length; i++) {
if (arr[i] < min) {
min = arr[i];
}
if (arr[i] > max) {
max = arr[i];
}
}
//创建辅助数组,数组大小为原数组的最大值
int[] helper = new int[max - min + 1];
for (int e : arr) {
//将原数组的元素作为辅助数组的下标,出现一次次数+1
//e - min计算偏差
helper[e - min]++;
}
//数据回填的位置
int current = 0;
//遍历辅助数组
for (int i = 0; i < helper.length; i++) {
//当前元素出现次数大于1,依次赋给原数组
while (helper[i] > 0) {
//每次赋值后,指针右移
//i + min回填数据计算偏差
arr[current++] = i + min;
helper[i]--;
}
}
return arr;
}
分析
时间复杂度
任何情况下 T(n) = O(n+k),其中 k 是原数组的最大值(即辅助数组的空间大小)。
稳定性
计数排序是稳定的。因为它是非比较排序,元素之间的顺序不依赖元素之间的比较。