计数排序(Counting Sort)

计数排序

计数排序(Counting Sort) 不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

算法描述

  1. 找出待排序的数组中最大和最小的元素;
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

算法分析

时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
\(O(n+k)\) \(O(n+k)\) \(O(n+k)\) \(O(n+k)\) 稳定

局限性:

  1. 当数列最大和最小值差距过大时,并不适合用计数排序。
  2. 当数列元素不是整数时,也不适合用计数排序。

例子

代码

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 的操作

  1. 完成统计元素个数
  2. 从第2个元素开始,每一个元素都加上前面所有元素之和。
  3. 从后向前遍历输入数列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;
posted @ 2022-02-24 08:55  morning-start  阅读(23)  评论(0编辑  收藏  举报