计数排序适合整数范围小、重复多的场景;不支持负数、浮点数、字符串等非整类型,需先校验极值并偏移下标,空输入和边界须显式处理,k < n 时性能优,否则不如通用排序。

计数排序适合什么场景?
计数排序不是万能的,它只在整数范围小、重复多时才真正快。比如对 []int{1, 2, 5, 3, 2, 1, 4} 排序,最大值是 5,最小值是 1,范围仅 5;但如果数组里有 999999 或负数很多(如 -1000000),count 数组会极大,内存直接爆掉。
- 范围判断必须做:先算
maxValue和minValue,再用maxValue - minValue + 1算长度,不能默认从 0 开始 - 不支持浮点、字符串、结构体等非整类型,强行转 int 容易溢出或截断
- 如果输入全是唯一的大整数(如
[1000001, 1000002, 1000003]),计数排序比sort.Ints还慢,因为要分配百万级切片
为什么原版实现常 panic 或结果错?
最常见两个坑:没处理负数、没做边界校验。
-
count[value] += 1会 panic:当value是负数时,索引越界 - 没减
minValue就直接当数组下标用,等于把负数映射到非法内存地址 - 累加阶段写成
for i := 1; i < len(count); i++没问题,但若count长度为 0(空输入)就会 panic,得先判len(arr) == 0
正确做法是:
- 先检查空切片:
if len(arr) == 0 { return arr } - 找极值时遍历一次,别只看
array[0]就初始化minValue,否则全负数时会出错 - 所有访问
count的地方都用arr[i] - minValue做偏移,不裸用arr[i]
稳定版和非稳定版怎么选?
稳定版保证相同元素的相对顺序不变,比如 [2a, 1, 2b] 排完是 [1, 2a, 2b];非稳定版可能是 [1, 2b, 2a]。
立即学习“go语言免费学习笔记(深入)”;
- 如果你只是排纯数字且不关心“哪个 2 先出现”,用简单循环重建法更直观:
for i := 0; i < len(count); i++ { for count[i] > 0 { result[index] = i + minValue index++ count[i]-- } } - 如果后续要扩展为基数排序,或数据带附属字段(比如
struct{val int; id string}),必须用稳定版:从原数组末尾开始填,靠累加后的count定位位置,并立刻count[val]--
性能对比和替代方案
计数排序理论是 O(n + k),其中 k 是值域宽度。但实际中:
-
k < n时,通常比sort.Ints快 2–3 倍(实测 10 万内 0–99 整数) -
k > 10*n时,内存占用和缓存失效让它比quickSort慢一个数量级 - Go 标准库的
sort.Ints是优化过的 introsort(快排+堆排+插排混合),通用性远强于计数排序
所以真实项目里:
- 别为了“学算法”硬套计数排序
- 真遇到高频小范围整数(比如 HTTP 状态码统计、像素灰度值排序),再手写
- 否则直接用
sort.Slice(nums, func(i, j int) bool { return nums[i] < nums[j] })更安全
值域大、数据杂、又想快?那该考虑桶排序或基数排序,而不是在这儿调 count 数组大小。


















