计数排序
原理
计数排序(Counting Sort) 使用了一个额外的数组 C,其中第 i 个元素是待排序数组A 中值等于 i 的元素的个数。然后根据数组 C 来将 A 中的元素排到正确的位置。其实计数排序其实是桶排序的一种特殊情况。
计数排序实现原理
- 创建数组C,找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项;
- 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加);
- 反向填充目标数组:将每个元素 i 放在新数组的第 C(i)项,每放一个元素就将 C (i)减去 1。
代码实现
public class CountSort {
public static void main(String[] args) {
int[] array = {5,2,2,6,9,9,3,3,4};
countSort(array);
System.out.println(Arrays.toString(array));
}
public static void countSort(int[] array) {
//求出待排序数组中的最大值和最小值,找出取值区间
int max = array[0];
int min = array[0];
for (int i = 0; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
if (min < array[i]) {
min = array[i];
}
}
//定义一个额外的数组C
int bucketSize = max - min + 1;
int[] bucket = new int[bucketSize];
//统计对应元素的个数,数组的下标也对应着元素值
for (int i = 0; i < array.length; i++) {
int bucketIndex = array[i] - min;
bucket[bucketIndex] += 0;
}
//对数组C内元素进行累加
for (int i = 1; i < bucket.length; i++) {
bucket[i] = bucket[i] + bucket[i - 1];
}
//创建临时数组R 存储最终有序IDE数据列表
int[] temp = new int[array.length];
//逆序扫描排序数组 保证元素的稳定性
for (int i = array.length-1; i >=0; i--) {
int bucketIndex = array[i] - min;
temp[bucket[bucketIndex] - 1] = array[i];
bucket[bucketIndex] -= 1;
}
for (int i = 0; i < temp.length; i++) {
array[i] = temp[i];
}
}
}
1:计数排序的时间复杂度是多少?
通过代码的实现过程我们发现计数排序不涉及元素的比较,不涉及桶内元素(数组C)的排序,只有对待排序数组和用于计数数组的遍历操作,因此计数排序的时间复杂度是 O(n+k),其中 k 是桶的个数即待排序的数据范围,是一种线性排序算法。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C 的长度 k 取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上 1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。
2:计数排序的空间复杂度是多少?
在计数排序的过程中需要创建额外的桶空间(数组 C)来计数,因此我们可以得知 计数排序的口空间复杂度为:O(n+K),其中 n 是数据规模大小,K 是计数排序中需要的桶的个数,其实也就是用来计数的数组 C 的长度,之前我们提到过它取决于待排序数组中数据的范围。
3:计数排序是稳定的排序算法吗?
在计数排序中核心操作中我们是逆序的去扫描待排序数组,这样仍然可以使待排序数组中值相同但是位置靠后的元素在最终的已排序数组中保持着相同的位置关系,因此计数排序是一个稳定的排序算法。
4:计数排序的适用场景?
计数排序只能用在数据范围不大的场景中,如果数据范围 k 比要排序的数据 n 大很多,就不适合用计数排序了。而且,计数排序只能给非负整数排序,如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下,转化为非负整数。