计数排序:原理、步骤、复杂度及应用全解析
一、基本原理
-
计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定小于x的元素个数。通过统计每个元素出现的次数,然后根据统计结果将元素放到有序序列中的正确位置。
-
假设输入的数组是A,长度为n,数组中的元素范围是0到k。它需要额外创建两个辅助数组:计数数组C(长度为k + 1)用于统计每个元素出现的次数,和输出数组B(长度为n)用于存放排序后的结果。
二、算法步骤
- 统计元素出现次数
- 遍历输入数组A,对于数组A中的每个元素A[i],将计数数组C[A[i]]的值加1。例如,若A = [2, 5, 3, 0, 2],当遍历到第一个元素2时,C[2]的值就加1。这个步骤可以用以下代码实现:
C = [0] * (k + 1) for i in range(len(A)): C[A[i]] += 1
- 计算元素的累积计数
- 计算计数数组C的累积和。累积和的意义是确定每个元素在排序后的输出数组B中的位置范围。具体来说,C[i]现在表示小于或等于i的元素的个数。代码如下:
for i in range(1, len(C)): C[i] += C[i - 1]
- 构建输出数组B
- 从输入数组A的末尾开始遍历(这样可以保证排序的稳定性),对于每个元素A[i],将其放到输出数组B中。B中的位置由C[A[i]] - 1确定(因为数组索引从0开始),然后将C[A[i]]的值减1。这个步骤可以用以下代码实现:
B = [0] * len(A) for i in range(len(A) - 1, -1, -1): B[C[A[i]] - 1] = A[i] C[A[i]] -= 1
- 返回排序后的数组
- 最后,输出数组B就是排序后的结果。
三、时间复杂度和空间复杂度
- 时间复杂度:
- 计数排序的时间复杂度为\(O(n + k)\),其中n是输入数组的长度,k是输入数组中元素的范围。在最好、最坏和平均情况下,时间复杂度都是\(O(n + k)\)。因为它主要包含三个步骤,统计元素出现次数、计算累积计数和构建输出数组,每个步骤的时间复杂度都与n或k有关。
- 空间复杂度:
- 空间复杂度为\(O(n + k)\)。因为需要创建计数数组C(长度为k + 1)和输出数组B(长度为n)来存储数据。不过,如果输入数组中的元素范围k较小,相对于基于比较的排序算法(如快速排序的平均空间复杂度\(O(log n)\)),计数排序在空间上可能会有优势。
四、稳定性和适用场景
- 稳定性:
- 计数排序是一种稳定的排序算法。稳定排序是指在排序过程中,相等元素的相对顺序不会改变。在计数排序的第三步构建输出数组B时,从输入数组A的末尾开始遍历,保证了相等元素的相对顺序不变。
- 适用场景:
- 计数排序适用于输入数据范围比较小的整数排序。例如,对学生成绩(分数范围通常是0 - 100)进行排序,或者对年龄(范围可能是0 - 120左右)等有限范围的数据进行排序。如果数据范围k很大(比如和n同数量级或者更大),那么计数排序可能会因为需要创建很大的计数数组C而占用过多的空间,效率可能不如其他排序算法。