相关笔记

概览

本节介绍 基数排序(Radix Sort) 算法,它是一种非比较排序算法,通过对关键字的每一位分别进行稳定排序来实现整体排序。基数排序的核心策略是LSD(Least Significant Digit)——从最低有效位开始,逐位向最高有效位进行排序。经过 趟稳定排序后,所有元素即按完整关键字排好序。

要点列表:

  • 基数排序使用稳定排序(通常为计数排序)作为子程序,对关键字的每一位分别排序
  • 位数,若每位有 个可能值,运行时间为 ====
  • 位二进制数,选择合适的基数 ,运行时间为 ====
  • 时,基数排序可在 ==== 时间内完成排序
  • 基数排序是现存最古老的仍在使用的排序算法,历史可追溯至1890年

知识结构总览

flowchart TD
    A["8.3 基数排序 (RADIX-SORT)"] --> B["算法思想"]
    A --> C["伪代码"]
    A --> D["正确性证明"]
    A --> E["复杂度分析"]
    A --> F["算法特性"]

    B --> B1["LSD策略: 从最低位开始"]
    B --> B2["每趟使用稳定排序"]
    B --> B3["d趟后完成排序"]

    C --> C1["RADIX-SORT(A, n, d)"]
    C --> C2["子程序: COUNTING-SORT"]

    D --> D1["引理8.3: 正确性"]
    D --> D2["关键条件: 稳定性"]

    E --> E1["引理8.3: Θ(d(n+k))"]
    E --> E2["引理8.4: Θ((b/r)(n+2^r))"]
    E --> E3["最优r的选择"]

    F --> F1["非比较排序"]
    F --> F2["非原地排序"]
    F --> F3["稳定排序"]

核心思想

核心思路

基数排序的基本策略是:将每个关键字拆分为若干”位”(digit),然后从最低有效位(LSD)开始,对每一位分别进行一次稳定排序。经过 趟排序后,所有元素即按完整关键字排好序。

为什么从最低位开始? 如果从最高位开始(MSD策略),需要对每个桶递归排序,产生大量中间堆,管理复杂。而从最低位开始,每趟排序后只需将所有桶按顺序拼接,无需递归,实现简洁。

为什么必须稳定? 稳定性保证:当某一位的值相同时,之前已排好的低位顺序不会被破坏。这是基数排序正确性的基石。

直观理解:扑克牌排序

想象你要将一副扑克牌按”花色+点数”排序。基数排序的做法是:

  1. 先按点数(低位)进行一次稳定排序
  2. 再按花色(高位)进行一次稳定排序

因为第二次排序是稳定的,所以相同花色的牌中,点数小的仍然在前面——最终结果正确。

算法执行流程

  1. 最低有效位(第1位)开始,到最高有效位(第d位)
  2. 对当前位使用稳定排序(如计数排序)对整个数组排序
  3. 重复 d 轮,每轮处理一位,最终得到完全排好序的数组
flowchart TD
    A["开始 RADIX-SORT"] --> B["i = 1(最低有效位)"]
    B --> C["对数组 A 按第 i 位进行稳定排序"]
    C --> D["i = i + 1"]
    D --> E{"i > d?"}
    E -- "否" --> C
    E -- "是" --> F["返回排好序的 A"]

RADIX-SORT 伪代码

RADIX-SORT(A, n, d)
1  for i = 1 to d
2      use a stable sort to sort array A[1..n] on digit i

RADIX-SORT

输入: 数组 ,其中每个元素是 位数,第1位是最低位,第 位是最高位 输出: 排序为非降序序列

算法步骤:

  1. (最低位)到 (最高位),依次对数组 按第 位进行稳定排序
  2. 通常使用 COUNTING-SORT 作为稳定排序子程序

优化提示: 如果使用 COUNTING-SORT 作为稳定排序子程序,可以预先分配输出数组,在 RADIX-SORT 的 for 循环中交替使用输入数组和输出数组,减少数据复制开销。

操作示例

以7个3位数为例,展示基数排序的执行过程(CLRS图8.3):

趟数排序依据结果
初始329, 457, 657, 839, 436, 720, 355
第1趟个位720, 355, 436, 457, 657, 329, 839
第2趟十位720, 329, 436, 839, 355, 457, 657
第3趟百位329, 355, 436, 457, 657, 720, 839

逐位分析:

  • 第1趟(个位): 按个位数字 0,5,6,7,7,9,9 排序后得到 720,355,436,457,657,329,839
  • 第2趟(十位): 在第1趟结果基础上,按十位数字 2,2,3,3,5,5,5 排序。注意 329 和 720 的十位都是2,但 720 排在前面——因为第1趟中 720 排在 329 前面,且第2趟排序是稳定的
  • 第3趟(百位): 按百位数字 3,3,4,4,6,7,8 排序,得到最终有序结果

正确性证明

引理 8.3

引理 8.3(基数排序的正确性与时间复杂度)

给定 位数,其中每个位可以取 个可能的值,如果基数排序使用的稳定排序耗时 ,则 RADIX-SORT 能在 时间内正确排序这些数。

证明(正确性——基于归纳法):

【数学归纳法(基础步i=1+归纳步第i位排序后1到i位有序)】

对正在被排序的位数 (从1到 )进行归纳。

  • 归纳基础(): 第1趟按最低位排序后,元素按最低位有序。这是平凡的——一趟稳定排序后自然按该位有序。
  • 归纳假设: 假设第 趟排序后,元素已按第 到第 位有序(即低位已经排好)。
  • 归纳步骤: 趟按第 位进行稳定排序。需要证明排序后元素按第 到第 位有序。

    【分情况讨论(a_i<b_i/a_i>b_i/a_i=b_i三种情况)】

    • 考虑任意两个元素 ,它们在第 到第 位上的值分别为
    • 情况一: 。第 趟排序后 排在 前面。正确。
    • 情况二: 。第 趟排序后 排在 前面。正确。
    • 情况三: 。由于排序是稳定的 的相对顺序与第 趟排序前相同。由归纳假设,在第 趟排序前它们已按第 位有序,因此排序后仍保持此顺序。正确。
  • 结论: 由数学归纳法, 趟排序后元素按全部 位有序。

稳定性的关键作用

上述证明的情况三是关键——当第 位的值相同时,必须保持之前已排好的低位顺序不变。这就是为什么基数排序的中间排序必须是稳定的。如果使用不稳定的排序(如快速排序),低位排序的结果会被高位排序破坏,最终结果不正确。

证明(时间复杂度):

【趟数乘以每趟时间(d趟Theta(n+k)得Theta(d(n+k)))】

每趟稳定排序耗时 (使用计数排序时),共 趟,因此总时间为:


复杂度深入分析

引理 8.4

引理 8.4(基数排序的位数分解)

给定 位二进制数和任意正整数 ,如果基数排序使用的稳定排序对范围 的输入耗时 ,则 RADIX-SORT 能在 时间内正确排序这些数。

证明:

【位数分解(b位键值视为d=ceil(b/r)个r位数字)】

将每个 位的键值视为 个”数字”,每个数字是 位的整数,取值范围为 。因此可以使用计数排序,其中

例如,一个32位整数可以视为4个8位数字:

每趟计数排序耗时 ,共 趟,因此总运行时间为:

【总时间计算(ceil(b/r)趟Theta(n+2^r)得Theta((b/r)(n+2^r)))】

最优基数 的选择

给定 ,如何选择 使 最小?

情况一:

【2^r < n时n+2^r=Theta(n)选r=b得线性时间】

此时 意味着 ,因此 。选择 ,运行时间为:

这是渐近最优的。

情况二:

【选r=floor(lg n)使2^r=Theta(n)得最优时间Theta(bn/lg n)】

选择 ,运行时间为:

这是最优选择,原因如下:

【最优性论证(r增大/减小两种方向均导致时间增大)】

  • 增大超过 时, 项增长快于分母中的 ,运行时间变为
  • 减小低于 时, 增大而 仍为 ,运行时间也增大

基数排序 vs 比较排序

渐近优势 vs 实际表现

(实践中常见)且 时,基数排序的运行时间为 ,看起来优于快速排序的期望 。但需要注意:

  • 常数因子: 基数排序的每趟可能比快速排序的一次比较-交换操作耗时更长
  • 缓存性能: 快速排序通常比基数排序更有效地利用硬件缓存
  • 空间开销: 使用计数排序的基数排序不是原地排序,需要额外 空间
  • 输入特性: 很大时(如64位浮点数),基数排序的优势减弱

补充理解与拓展

基数排序的历史——从人口普查到IBM

基数排序是现存最古老的仍在使用的排序算法,其历史甚至早于电子计算机的发明。

关键历史事件:

  • 1880年美国人口普查耗时近8年完成数据处理,由于人口增长迅速,1890年的普查面临无法在宪法规定的10年内完成的危机
  • 1890年,Herman Hollerith发明了使用基数排序原理的机电制表机(tabulating machine),用于处理美国人口普查数据
  • Hollerith的机器在打孔卡片的特定列上检测孔位,将卡片分入12个箱子——这正是基数排序的物理实现
  • 使用Hollerith机器后,1890年人口普查仅耗时2年就完成了数据处理,相比1880年缩短了6年
  • Hollerith的公司 Tabulating Machine Company 后来经过多次合并,最终成为 IBM(International Business Machines)

基数排序在计算机博物馆中仍能看到其原始形态——卡片排序机(card sorter),它能在80列的打孔卡上按指定列进行机械分拣。

基数排序的现代变体与工程应用

基数排序在当代计算机科学中仍有广泛的应用和活跃的研究:

现代变体:

  1. MSD(Most Significant Digit)基数排序: 从最高位开始,递归处理每个桶。适合不等长字符串排序,但需要更多额外空间
  2. LSD(Least Significant Digit)基数排序: 从最低位开始,CLRS采用的版本。实现简洁,不需要递归,但要求所有关键字等长
  3. American Flag Sort: McIlroy & Bostic (1993) 提出,结合MSD基数排序和原地分区技术,用于字符串排序,减少了额外空间需求
  4. GPU并行基数排序: NVIDIA Thrust库中的 radix_sort 是GPU上最快的排序算法之一,利用GPU的大规模并行性同时处理多个键值位

数据库系统中的应用:

  • PostgreSQL 和 MySQL 在处理 ORDER BY 查询时,对整数类型且值域有限的情况会使用基数排序优化
  • 数据库中的排序操作通常涉及大量数据,基数排序的线性时间特性在此场景下优势明显

参考文献: McIlroy, P. M. & Bostic, K. (1993). “Engineering radix sort.” Computing Systems, 6(1), 5-27.


易混淆点与辨析

误区:基数排序从最高位开始排序

错误理解: “基数排序应该先按最高位排序,就像我们比较数字时先看最高位一样”

正确理解: CLRS采用的是LSD(最低有效位优先)策略,从最低位开始排序。虽然直觉上从最高位开始更自然(MSD策略),但MSD需要递归处理每个桶,产生大量中间堆,管理复杂。LSD策略只需 趟线性扫描,无需递归,实现更简洁。

MSD vs LSD 对比:

维度MSDLSD
排序方向最高位 → 最低位最低位 → 最高位
是否需要递归是,每个桶递归排序否, 趟线性扫描
空间开销较大(递归栈+桶存储)较小(仅需两个数组交替)
适用场景不等长字符串排序等长关键字排序
CLRS采用

误区:基数排序的中间排序不需要稳定

错误理解: “只要每趟排序正确,最终结果就正确,稳定性无所谓”

正确理解: 稳定性是基数排序正确性的必要条件。如果中间排序不稳定,当某一位的值相同时,之前已排好的低位顺序会被打乱,导致最终结果错误。

反例: 对两位数 排序

  • 第1趟(个位)稳定排序:(个位1,2,3已有序)
  • 第2趟(十位)不稳定排序:(结果不确定!)
  • 第2趟(十位)稳定排序:(结果唯一且正确)

在不稳定排序中,十位同为2的22和31的相对顺序可能被打乱,而稳定排序保证了22始终在31前面(因为第1趟中22在31前面)。


习题精选

题号题目描述难度
8.3-1模仿图8.3,展示 RADIX-SORT 对英文单词列表的排序过程
8.3-2插入排序、归并排序、堆排序、快速排序中哪些是稳定排序?如何使任何比较排序稳定?⭐⭐
8.3-3用归纳法证明基数排序的正确性,指出证明中哪里需要稳定性假设⭐⭐
8.3-4若 COUNTING-SORT 作为稳定子程序被调用 次,共产生 遍数据扫描,如何减少到 遍?⭐⭐⭐
8.3-5展示如何在 时间内对范围 个整数排序⭐⭐
8.3-6MSD基数排序在最坏情况下需要多少趟排序?需要跟踪多少堆卡片?⭐⭐⭐

视频学习指南

资源主题链接说明
MIT 6.006 Lecture 5Sorting II: Heapsort, Radix Sorthttps://www.youtube.com/watch?v=nu4gDuFabIMMIT公开课,含基数排序完整讲解
Abdul BariRadix Sort Algorithmhttps://www.youtube.com/watch?v=XiuSWmz7VRo逐步动画演示LSD基数排序过程
HackerRankRadix Sorthttps://www.youtube.com/watch?v=Il45xNUHG1k实际代码实现演示
WilliamFisetRadix Sorthttps://www.youtube.com/watch?v=XeG1-6R_ZI0排序算法系列中的基数排序专题
GeeksforGeeksRadix Sorthttps://www.youtube.com/watch?v=8mBsrR1q0no含复杂度分析和代码实现

教材原文

CLRS 第4版 8.3节原文

Radix sort solves the problem of card sorting—counterintuitively—by sorting on the least significant digit first. The algorithm then combines the cards into a single deck, with the cards in the 0 bin preceding the cards in the 1 bin preceding the cards in the 2 bin, and so on. Then it sorts the entire deck again on the second-least significant digit and recombines the deck in a like manner. The process continues until the cards have been sorted on all digits. Remarkably, at that point the cards are fully sorted on the -digit number.

In order for radix sort to work correctly, the digit sorts must be stable. The sort performed by a card sorter is stable, but the operator must be careful not to change the order of the cards as they come out of a bin, even though all the cards in a bin have the same digit in the chosen column.


参见Wiki

第08章-线性时间排序 基数排序