计数排序(Counting Sort)是一种非比较排序算法,它通过统计数组中元素出现的次数来确定每个元素在有序数组中的位置,最终完成排序。它适用于数据范围较小且非负整数的情况。
1. 计数排序的基本原理
计数排序的核心思想是统计每个元素的出现次数,然后根据统计结果,将元素放入输出数组中的正确位置。具体步骤包括:
1. 统计阶段:
遍历输入数组,统计每个元素的出现次数,存储在一个计数数组中。
2. 累积阶段:
将计数数组转换为累积和数组,表示每个元素在输出数组中的位置范围。
3. 输出阶段:
从输入数组的末尾开始,按照累积和数组中的位置,将元素放入输出数组,并更新计数数组。
2. 计数排序的特点
1. 非比较排序:
计数排序不通过比较元素大小来排序,而是通过统计元素出现次数进行排序。
2. 适用范围:
• 输入元素是非负整数或可以映射到非负整数的情况。
• 元素的取值范围 k 不能太大,否则会浪费空间。
3. 时间复杂度:
O(n + k) ,其中 n 是输入数组的大小, k 是输入元素的取值范围。
4. 空间复杂度:
O(k) ,需要额外的计数数组和输出数组。
5. 稳定性:
计数排序是稳定排序,相等元素的相对顺序在排序后保持不变。
3. 计数排序的工作流程
统计阶段
示例:排序数组 A = [4, 2, 2, 8, 3, 3, 1]
• 找出数组中的最大元素 k = 8 。
• 初始化计数数组 C ,大小为 k+1 ,初始值都为 0。
• 遍历数组 A ,统计每个元素的出现次数。
累积阶段
在累积阶段,我们将计数数组转化为累积和数组,用来确定每个元素在最终输出数组中的位置。
目的:
• 对于数组中每个元素 i ,累积和数组 C[i] 表示小于等于 i 的元素个数。
• 这样一来,数组 A 中的每个元素 i 的最终位置可以通过 C[i] 来确定。
示例
假设输入数组 A = [4, 2, 2, 8, 3, 3, 1] ,最大值 k = 8 。
1. 计数数组 C (统计元素出现次数):
2. 累积和数组 C (累积阶段):
从 C[1] 开始,将每个元素与前一个元素的值相加:
C[i] = C[i] + C[i-1]
• 含义:
• C[1] = 1 :小于等于 1 的元素有 1 个。
• C[2] = 3 :小于等于 2 的元素有 3 个。
• C[3] = 5 :小于等于 3 的元素有 5 个。
• C[8] = 7 :小于等于 8 的元素有 7 个。
输出阶段
在输出阶段,我们从输入数组 A 的末尾开始遍历,将每个元素放入输出数组 B 的正确位置。
实现方法
1. 对于每个元素 A[i] :
• 使用累积和数组 C 确定 A[i] 在输出数组中的位置:
• 将 减 1,表示下一个相同元素应该放在前一个位置。
2. 从后往前遍历输入数组,可以确保排序的稳定性:
• 如果输入数组中有两个相等的元素,原数组中后出现的元素会被放到输出数组的后面,从而保持稳定性。
示例
继续以上示例 A = [4, 2, 2, 8, 3, 3, 1] ,累积数组为:
• 初始化输出数组 B (与输入数组大小相同)。
• 从后向前遍历 A ,并将元素放入 B 中:
输出结果
最终输出数组为:
B = [1, 2, 2, 3, 3, 4, 8].
4. 计数排序的伪代码
def counting_sort(A):
# A: 输入数组
k = max(A) # 找到数组中的最大值
n = len(A)
B = [0] * n # 输出数组
C = [0] * (k + 1) # 计数数组
# 1. 统计每个元素的出现次数
for i in range(n):
C[A[i]] += 1
# 2. 计算累积和
for i in range(1, k + 1):
C[i] += C[i - 1]
# 3. 将元素放入输出数组(从后向前遍历,保证稳定性)
for i in range(n - 1, -1, -1):
B[C[A[i]] - 1] = A[i]
C[A[i]] -= 1
return B
5. 时间复杂度分析
• 统计阶段:遍历数组 A ,时间复杂度 O(n) 。
• 累积阶段:遍历计数数组 C ,时间复杂度 O(k) 。
• 输出阶段:再次遍历数组 A ,时间复杂度 O(n) 。
因此,总时间复杂度为:
其中:
• n :输入数组的大小。
• k :输入元素的取值范围(最大值)。
6. 计数排序的应用场景
1. 输入数据是非负整数或可以映射到整数的情况。
2. 元素范围 k 相对于 n 较小,否则空间开销较大。
3. 适用于需要稳定排序的场景。
7. 总结
• 计数排序是一种非比较排序,通过统计元素出现次数实现排序。
• 时间复杂度为 ,适用于元素范围较小的整数排序。
• 计数排序是稳定排序,可以保持相等元素的相对顺序。