计数排序(Counting Sort)
计数排序
计数排序(Counting Sort) 不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
算法描述
- 找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
算法分析
时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|
\(O(n+k)\) | \(O(n+k)\) | \(O(n+k)\) | \(O(n+k)\) | 稳定 |
局限性:
- 当数列最大和最小值差距过大时,并不适合用计数排序。
- 当数列元素不是整数时,也不适合用计数排序。
例子
代码
Java
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];
for(int i=0; i<array.length; i++){
countArray[array[i]]++;
}
//3.遍历统计数组,输出结果
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;
}
python
def countingSort(arr, maxValue):
bucketLen = maxValue+1
bucket = [0]*bucketLen
sortedIndex =0
arrLen = len(arr)
for i in range(arrLen):
if not bucket[arr[i]]:
bucket[arr[i]]=0
bucket[arr[i]]+=1
for j in range(bucketLen):
while bucket[j]>0:
arr[sortedIndex] = j
sortedIndex+=1
bucket[j]-=1
return arr
优化
当所需排序的数列只是在一个部分区间,而不是从 0 开始的时候,就会浪费许多空间。
为了避免空间浪费,我们不再以输入数列的最大值+1作为统计数组的长度,而是以数列最大值-最小值+1作为统计数组的长度即可。
public static int[] countSortV2(int[] array) {
//1.得到数列的最大值和最小值,并算出差值d
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.遍历统计数组,输出结果
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 + min;
}
}
}
但是,当我们排序不是依靠待排序元素自身,而是依靠其特性的时候,例如成绩排名(name:score)
排序的是名字,要根据成绩来排序
此时我们就不能直接输出 sortArray,而需要遍历初始数组才能正确找到 name:value
的对应关系。这就使得我们要对 countArray 进行转换,来方便获得 score:排序后的index
的映射,最后正向逻辑就是 nmae -> scort -> index
,即构建一个 name:index
的映射
具体对 countArray 的操作
- 完成统计元素个数
- 从第2个元素开始,每一个元素都加上前面所有元素之和。
- 从后向前遍历输入数列Array,
countArray[array[i]-min]-1
就是 index 。(其中, name:i, score:array[i] 为了简化代码就没有)
//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]=i;
countArray[array[i]-min]--;
}
return sortedArray;