基数排序适合百万级固定长度非负整数且分布均匀的场景,时间复杂度o(n),优于sort.ints的o(n log n);但不适用浮点数、变长字符串、负数,易因桶大小错误或符号位未处理引发panic。

Go 语言本身不提供内置的基数排序(Radix Sort),但可以用标准库类型手动实现;它适合整数(尤其是非负整数)且在特定数据分布下比 sort.Ints 更快,但不是通用替代品。
为什么不用 sort.Ints 而选基数排序?
当待排序数据量大(如百万级)、键长固定(如 32 位或 64 位整数)、且分布较均匀时,基数排序可达到 O(n) 时间复杂度,而 sort.Ints 是 O(n log n) 的内省排序。但它不适用于浮点数、字符串(除非统一长度+填充)、负数(需额外偏移处理)。
常见错误现象:panic: runtime error: index out of range —— 桶数组大小设错,或未处理符号位。
- 只推荐用于
[]uint32、[]uint64或已转为非负的[]int32(如加1 偏移) - 若数据中 90% 都是相同前缀(如大量
0x0000xxxx),桶计数阶段可能浪费内存 - 对小数组(sort.Ints 慢,因常数开销高
按字节分桶:用 uint8 桶实现 32 位整数排序
核心思路是循环 4 轮(因为 uint32 = 4 字节),每轮按当前字节值(0–255)分到 256 个桶中,再顺序收集。关键在稳定性和索引计算——必须从最低有效字节(LSB)开始,否则结果错误。
示例片段(仅示意逻辑,不含完整边界检查):
func radixSortUint32(arr []uint32) {
buckets := make([][]uint32, 256)
for shift := uint(0); shift > shift) & 0xFF
buckets[bucketIdx] = append(buckets[bucketIdx], x)
}
// 收集回原数组
idx := 0
for _, bucket := range buckets {
for _, x := range bucket {
arr[idx] = x
idx++
}
}
}
}
- 参数
shift必须从0开始,不能从高位起 —— 否则稳定性被破坏 - 桶切片
buckets复用底层数组,避免每轮make开销 - 若排序
[]int32,先整体转为uint32:用math.Float32bits不适用,正确做法是binary.LittleEndian.PutUint32或直接uint32(x) ^ 0x80000000(符号位翻转)
64 位整数和性能陷阱
对 []uint64,需 8 轮循环,桶数仍为 256(不是 65536),但内存局部性下降更明显。实测中,当元素超过 10M 时,GC 压力可能上升 —— 因为每轮都要追加到桶切片,触发多次底层数组扩容。
- 优化方式:预分配每个桶容量(用
make([]uint64, 0, approx)),approx 可基于历史频率直方图估算 - 避免在循环内调用
len()判断桶空 —— 直接遍历所有 256 桶,空桶跳过即可,现代 CPU 分支预测对此很友好 - 错误配置:把
shift步长设成 4(半字节)—— 虽然可行,但桶数变成 16,需 16 轮,实际性能通常更差
真正难的是负数处理和内存复用粒度:一个没注意,就从 O(n) 退化成 O(n log n) 或 panic。别为了“炫技”替换所有排序场景,先 profile 看瓶颈是不是真在这里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











