一、简介
计算排序假设 n 个输入元素都是 0 到 k 区间内的一个整数,其中 k 为某个整数。
基本原理:
创建一个长度为 k+1 的数组 count[],它的 count[i] 的值对应输入数组中 i 出现的次数。通过遍历一次输入数组并统计每个元素出现次数,最后遍历 count[] 输出。
二、时间复杂度
计算排序的时间复杂度为 O(n)。
计算排序不是比较排序算法,它没有元素之间的比较操作(判断大于或者小于),而我们之前所了解到的所有的比较排序算法的时间复杂度下界均为 Ω(n * lg n)。
三、实现
public class Main {
public static void main(String[] args) {
// 输入元素均在 [0, 10) 这个区间内
int[] arr = new int[] { 5, 4, 6, 7, 5, 1, 0, 9, 8, 1 };
countSort(arr);
printArray(arr);
}
public static void countSort(int[] arr) {
int[] count = new int[10];
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++; // 计数每个元素出现次数
}
int index = 0;
for (int i = 0; i < count.length; i++) {
while (count[i] > 0) {
arr[index++] = i;
count[i]--;
}
}
}
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
运行结果:
0 1 1 4 5 5 6 7 8 9
四、扩展
前面我们看到,计数排序的前提是输入元素都是在 [0, k] 这样的一个区间内。那么假如我们存在负数的输入,或者我们也不确定输入元素的具体范围是多少,又该怎么变通呢?
其实,以上情况也不难解决。我们只需要多遍历一次数组,找到元素的最大值 max 和最小值 min,然后依旧创建一个长度为 max - min + 1 长度的数组,其中 min 是可能为负数的。然后我们引入一个变量 offset 来修正数值 i 在计数数组 count[] 中的正确位置。
代码实现:
public class Main {
public static void main(String[] args) {
// 输入元素均在 [0, 10) 这个区间内
int[] arr = new int[] { -3, 15, -12, 0, 48, 41, -8, 23, 33, 33 };
countSort(arr);
printArray(arr);
}
public static void countSort(int[] arr) {
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
int offset = 0 - min;
int[] count = new int[max - min + 1];
for (int i = 0; i < arr.length; i++) {
count[arr[i] + offset]++; // 计数每个元素出现次数
}
int index = 0;
for (int i = 0; i < count.length; i++) {
while (count[i] > 0) {
arr[index++] = i - offset;
count[i]--;
}
}
}
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
运行结果:
-12 -8 -3 0 15 23 33 33 41 48
结果也是正确的。
五、总结
可以看到,计数排序是我们目前接触到的排序算法中,时间复杂度最低的,仅为 O(n)。
但其也存在一定的限制,它比较适合我们已知输入元素的取值范围的情况。