Skip to content

选型与应用场景

基于通用算法套路 · 核于 2026-07

速查

  • 选型三问:①数据是整数且值域小吗?→ 计数;②数据分布近似均匀吗(尤其浮点)?→ 桶;③数据有固定位数可按位分解吗(手机号/字符串)?→ 基数;都不满足 → 退回比较排序。
  • 计数排序的黄金场景:值域 k = O(n) 的整数或可映射为整数的类型——年龄(0~150)、考试分数(0~100)、字符 ASCII(0~127)。只要 k 不大,计数排序是这几种数据的最快解。
  • 桶排序的黄金场景:值域连续且分布近似均匀的浮点数(如 [0, 1) 均匀分布),桶数 m = Θ(n) 时期望 O(n);也适合「值域大但分布均匀」无法直接计数的场景。
  • 基数排序的黄金场景固定位数的多关键字——手机号(11 位)、身份证号、定长字符串字典序、IPv4 地址(4 段 8 位)。位数 d 是常数时 O(d · n) ≈ O(n)。
  • 稳定性速查:计数排序(倒序放)稳定;基数排序(每趟稳定)稳定;桶排序取决于桶内排序——桶内用插排则稳定,用快排则不稳定。
  • 局限性铁律:值域 k 大时计数排序空间 O(k) 爆炸;分布倾斜时桶排序退化到 O(n²);位数 d 大或不可拆位时基数排序失效——前提不满足就退回比较排序
  • 负数处理:计数排序统一减 min 做偏移把值域平移到 [0, k);基数排序负数需单独分组(按补码或绝对值分别处理),通常更推荐计数偏移法。
  • 实际应用:①计数/基数排序作为子例程出现在字符串排序、后缀数组构造;②LSD 基数用于定长字符串字典序(旧式卡片机排序的遗产);③MSD 基数用于变长字符串前缀排序;④桶思想用于均匀分片的负载均衡 / 哈希分桶
  • 工程现实:各语言默认排序仍是比较排序或其混合(Timsort / 内省排序 / 双轴快排)——非比较排序只在「值域小整数」这类确定满足前提的场景才登场。

一、何时用计数排序:值域小的整数

判定条件:数据是整数(或可一对一映射为整数),且值域 k 与元素数 n 同阶或更小(k = O(n))。

为什么:计数排序时间 O(n + k)、空间 O(k)。当 k = O(n) 时,整体就是 O(n),且常数极小(只做加减与下标定位,无比较函数调用)。一旦 k ≫ nO(k) 的频次数组空间和初始化时间会成为灾难。

典型场景

  • 年龄排序:值域 0~150,k=151,对任意 n 都极快。
  • 考试分数排序:百分制 0~100,k=101
  • 字符 / 字节频率统计:ASCII k=128 或字节 k=256,这也是基数排序每趟的内部子例程。
  • 小范围枚举:颜色值(0~255)、星期(1~7)、月份(1~12)。

反例(不该用):对 10 个 32 位整数排序,k ≈ 4×10⁹,频次数组要 4GB——远比直接插排慢。

二、何时用桶排序:均匀分布的浮点数

判定条件:值域连续(尤其浮点数),分布近似均匀,无法或不宜直接计数。

为什么:把值域均分成 m = Θ(n) 个桶后,均匀分布下每桶平均 n/m = O(1) 个元素,桶内排序近乎常数,整体期望 O(n)。桶排序的关键不是「桶内用什么排序」,而是「分布是否均匀让每桶都轻」。

典型场景

  • [0, 1) 均匀分布浮点数:经典教科书设定,m = n 个桶,期望 O(n)。
  • 值域大但分布均匀的数据:如传感器读数、随机散布的大范围数值,无法计数但分布均匀。
  • 均匀分片 / 哈希分桶:桶思想在分布式负载均衡、一致性哈希中复用——把 key 均分到桶(节点)。

反例(不该用):数据严重倾斜(90% 落入同一桶),退化到该桶内排序,最坏 O(n²)。

三、何时用基数排序:固定位数的多关键字

判定条件:数据可按位分解(整数按数位、字符串按字符),且位数 d 不大(常数或 O(log n))。

为什么:基数排序做 d 趟稳定计数排序,每趟 O(n + b),合计 O(d · (n + b))。当 d 是常数(定长数据)时,整体 O(n),且稳定——这对多关键字排序(先按次要关键字排,再按主关键字稳定排)特别有价值。

典型场景

  • 手机号 / 身份证号排序:固定位数(11 位 / 18 位),LSD 基数 O(11n) / O(18n) ≈ O(n)。
  • 定长字符串字典序:如 8 位日期 YYYYMMDD、定长编码,LSD 按字符位排序。
  • 日期 / 时间排序:可拆为「年-月-日-时-分-秒」多位关键字。
  • IPv4 地址排序:4 段 8 位,按段做基数。
  • 多关键字排序:先按次要键排,再按主键稳定排(基数排序思想的推广)。

MSD vs LSD

  • LSD(低位到高位):实现简单,迭代 d 趟,适合定长数据。
  • MSD(高位到低位):递归分桶,适合变长字符串字典序(短的排前),但实现复杂、递归开销大。

反例(不该用):任意可比较对象(无法拆位)、位数 d 很大的大整数(d 大时 O(d · n) 不见得比 O(n log n) 好)、浮点数(位模式与大小关系非单调,需特殊处理)。

四、稳定性:不只是特性,是正确性

非比较排序里稳定性地位特殊:

算法稳定?原因
计数排序✅ 稳定第三步「倒序遍历 + 前缀和定位」保证同值元素后出现的仍排后
桶排序取决桶内桶间天然有序;桶内用插排 / 归并则稳定,用快排则不稳定
基数排序✅ 稳定每一趟用稳定计数排序,稳定性是 LSD 正确的前提

为什么基数排序必须稳定:从低位排到高位时,若某趟不稳定,高位排序会打乱低位已排好的相对次序——比如 [21, 12] 按个位排成 [21, 12](1 同),再按十位若不稳定可能得到 [12, 21](对)但也可能错乱。稳定性保证「同高位元素保持低位趟的次序」,从而最终全局有序。

对计数排序「倒序放」的理解:前缀和后 count[v] 指向值 v 的末尾下标 + 1。倒序遍历原数组时,后出现的同值元素先被放进靠后的位置(--count 先减),从而保持原相对次序——这就是稳定性的来源。若改成顺序放则不稳定。

五、局限性与负数处理

非比较排序的命门在于前提,三个常见陷阱:

  • 值域 k 过大 → 空间爆炸:计数排序要 O(k) 空间。对 32 位整数 k ≈ 4×10⁹,频次数组不可接受。应对:只在 k = O(n) 时用计数;否则换基数(按位)或退回比较排序。
  • 分布倾斜 → 桶排序退化:全挤一个桶时退化到桶内排序最坏 O(n²)。应对:先估分布,倾斜则不用桶排序;或桶数动态调整。
  • 位数 d 过大 / 不可拆位 → 基数失效:大整数 d 大、浮点位模式非单调、任意对象不可拆位。应对:只对固定位数整数 / 字符串用基数。

负数处理

  • 计数排序:统一减最小值 x - min 做偏移,把值域 [min, max] 平移到 [0, k),输出时还原。这是最干净的负数处理法。
  • 基数排序:负数较麻烦——十进制下负号与位不兼容。常见做法是把负数、非负数分组,各自基数排序后拼接(负数按绝对值排再反转);或用补码按无符号位模式排序(但需理解补码的符号位)。通常负数场景更推荐计数偏移法

六、实际工程应用

  • 基数排序作为字符串排序的子例程:后缀数组构造(SA-IS、DC3 的某些步骤)、定长键的字典序排序。
  • LSD 基数的历史地位:早期卡片机(打孔卡)排序就是 LSD 基数——按列(位)分桶收集,是计算机出现前就有的排序方法。
  • 桶思想的外延:负载均衡(把任务按 key 均分到节点)、一致性哈希(虚拟节点分桶)、数据库直方图统计。
  • 混合排序中的计数身影:当快排 / Timsort 递归到小子数组且值域小时,部分实现会切换到计数排序以利用其小常数——这也是非比较排序最常见的「藏身之处」。

交互演示

下一步

选型掌握后,最后一份是速查参考——复杂度对比表、可直接复用的代码模板、选型决策树、与比较排序的横向对比、以及高频易错点清单,见参考