北屋教程网

专注编程知识分享,从入门到精通的编程学习平台

计数排序(Counting Sort)被忽略了?大学课本上好像没有提到?

当按学生的分数或年龄排序时,可以通过统计元素出现的次数来实现。它的核心思想是 “统计频率,按序填充”。我一直不知道有个计数排序(Counting Sort)的说法,所以心里一直迷惑这么简单基础的排序算法怎么没有在书上读到?大学课本上好像没有提到计数排序(Counting Sort),你有没有同感?难道是我读大学时太不认真看书了?

问:我是一个研究生毕业的程序员,请给我解释一下基数排序算法

DeepSeek:

基数排序(Radix Sort)是一种非比较型的整数排序算法,它通过逐位处理数字的每一位来实现排序。基数排序的核心思想是“按位分配,多次收集”,适用于整数或固定格式的字符串排序。以下是详细解释:

核心思想

  1. 按位排序:从最低位(LSB)到最高位(MSB),依次对每一位进行排序(例如个位、十位、百位等)。
  2. 稳定性:每次按位排序必须使用稳定排序算法(如计数排序),保证相同数字的相对顺序不变。

算法步骤

  1. 确定最大位数:找到数组中最大数字的位数(记为 d),决定需要排序的轮数。
  2. 从低位到高位排序
  • 对每一位(个位、十位、…)执行一次稳定排序(通常用计数排序)。

3. 合并结果:每一轮排序后,数字会根据当前位有序,最终所有位处理完后整体有序。

示例

假设数组为 [170, 45, 75, 90, 802, 24, 2, 66],步骤如下:

  1. 最大位数 d=3(802有3位)。
  2. 按个位排序
  • 分组:[170, 90], [802, 2], [24], [45, 75], [66]
  • 合并:[170, 90, 802, 2, 24, 45, 75, 66]
  1. 按十位排序
  • 分组:[802, 2], [24], [45], [66], [170, 75], [90]
  • 合并:[802, 2, 24, 45, 66, 170, 75, 90]
  1. 按百位排序
  • 分组:[2, 24, 45, 66, 75, 90], [170], [802]
  • 合并:[2, 24, 45, 66, 75, 90, 170, 802](最终有序)

时间复杂度

  • 最优/平均/最坏:(O(d \cdot n)),其中 d 是最大位数,n 是元素数量。
  • 空间复杂度:(O(n + k))(k 是计数排序的桶大小,通常 k=10 表示十进制数字)。

关键点

  1. 稳定性要求:每轮排序必须稳定,否则高位排序会破坏低位已排好的顺序。
  2. 适用范围:适合整数或定长字符串,不适合浮点数或可变长数据。
  3. 与计数排序的关系:基数排序常利用计数排序作为子过程。

代码实现(Python)

def counting_sort(arr, exp):
n = len(arr)
output = [0] * n
count = [0] * 10 # 0-9的计数器
# 统计当前位的数字出现次数
for num in arr:
digit = (num // exp) % 10
count[digit] += 1
# 计算累加次数(确定输出位置)
for i in range(1, 10):
count[i] += count[i - 1]
# 反向填充(保证稳定性)
i = n - 1
while i >= 0:
num = arr[i]
digit = (num // exp) % 10
output[count[digit] - 1] = arr[i]
count[digit] -= 1
i -= 1
# 将结果拷贝回原数组
for i in range(n):
arr[i] = output[i]
def radix_sort(arr):
max_num = max(arr)
exp = 1 # 从个位开始
while max_num // exp > 0:
counting_sort(arr, exp)
exp *= 10 # 处理十位、百位...
# 测试
arr = [170, 45, 75, 90, 802, 24, 2, 66]
radix_sort(arr)
print("排序后:", arr) # 输出: [2, 24, 45, 66, 75, 90, 170, 802]

为什么有效?

  • 低位优先:先处理低位可以逐步细化排序,高位最终决定全局顺序。
  • 稳定性保证:相同高位的数字在后续排序中会按低位顺序排列。
    基数排序在数据范围不大且位数较少时效率极高(如手机号排序),但在数据范围大时可能不如快速排序等通用算法。

问:那 计数排序 又是怎样的呢?

DeepSeek:

计数排序(Counting Sort)是一种非比较型的整数排序算法,通过统计元素出现的次数来实现排序。它的核心思想是 “统计频率,按序填充”,适用于整数范围较小且已知的情况(如年龄、成绩等)。以下是详细解释:

核心思想

  1. 统计频率:统计每个元素在数组中出现的次数。
  2. 计算位置:通过累加频率确定每个元素在有序数组中的最终位置。
  3. 填充结果:按统计结果将元素放入正确位置,保证稳定性。

算法步骤

  1. 统计频率
  • 遍历数组,统计每个整数出现的次数,存入计数数组 count
  1. 累加频率
  • count 数组进行累加操作,count[i] 表示值小于等于 i 的元素个数。
  1. 反向填充

- 从原数组末尾开始遍历,根据 count 数组确定当前元素的位置,放入结果数组,同时减少计数(保证稳定性)。

示例

假设数组为 [4, 2, 2, 8, 3, 3, 1],范围是 1~8k=8):

  1. 统计频率
  • count = [0, 1, 2, 2, 1, 0, 0, 0, 1]
    (索引 0 无用,count[1]=1 表示 1 出现 1 次,count[2]=2 表示 2 出现 2 次,依此类推)
  1. 累加频率
  • count = [0, 1, 3, 5, 6, 6, 6, 6, 7]
    count[2]=3 表示值 ≤2 的元素有 3 个)
  1. 反向填充
  • 遍历原数组最后一个元素 1count[1]=1 → 放入结果索引 1-1=0 的位置,count[1] 减 1。
  • 重复过程,最终得到有序数组 [1, 2, 2, 3, 3, 4, 8]

时间复杂度

  • 最优/平均/最坏:(O(n + k)),其中 n 是元素数量,k 是整数范围(如 0~100k=101)。
  • 空间复杂度:(O(n + k))(需要结果数组和计数数组)。

关键点

  1. 适用范围:仅适用于 整数范围较小 的数据(如 0~100)。
  2. 稳定性:通过反向填充保证相同值的原始顺序不变(对基数排序至关重要)。
  3. 局限性:若 k 远大于 n(如排序 [1, 1000000]),则空间浪费严重。

代码实现(Python)

def counting_sort(arr):
if not arr:
return arr
max_val = max(arr)
min_val = min(arr)
k = max_val - min_val + 1 # 计算实际范围(优化空间)
# 初始化计数数组
count = [0] * k
output = [0] * len(arr)
# 统计频率
for num in arr:
count[num - min_val] += 1 # 偏移到 0-based
# 累加频率
for i in range(1, k):
count[i] += count[i - 1]
# 反向填充(保证稳定性)
for i in range(len(arr) - 1, -1, -1):
num = arr[i]
pos = count[num - min_val] - 1 # 计算位置
output[pos] = num
count[num - min_val] -= 1
return output
# 测试
arr = [4, 2, 2, 8, 3, 3, 1]
sorted_arr = counting_sort(arr)
print("排序后:", sorted_arr) # 输出: [1, 2, 2, 3, 3, 4, 8]

优化与变种

  1. 范围偏移:通过 min_val 偏移(如 -100~1000~200),减少 k 的值。
  2. 负数支持:通过偏移处理负数(如 min_val 为负数时,num - min_val 转为非负)。
  3. 非整数扩展:需转换为整数(如浮点数乘 10 的幂次转为整数)。

为什么基数排序用计数排序?

基数排序的每一轮需要 稳定地 按某一位排序,而计数排序:

  • 是稳定的;
  • 时间复杂度 (O(n + k))(k=10 处理十进制位),高效且常数小。

对比其他排序

算法

是否比较排序

时间复杂度

适用场景

计数排序

(O(n + k))

小范围整数

快速排序

(O(n \log n))

通用排序

基数排序

(O(d \cdot n))

定长整数/字符串

计数排序是基数排序的基础,理解它有助于掌握更复杂的非比较排序算法!

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言