多轮分配与收集的稳定排序
基数排序(Radix Sort)是一种**非比较型**整数排序算法。它的核心思想是将整数按**位数**(或字符串按字符)切割成不同的数字,然后按每个位数(或字符位置)分别进行排序。它依赖于一个稳定的内部排序算法(通常是计数排序)来处理每一位的排序。
基数排序主要有两种方式:
基数排序特别适用于以下场景:
| 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| 基数排序 (Radix Sort) | O(d·(n+k)) 或 O(d·n) | O(d·(n+k)) 或 O(d·n) | O(n+k) | 稳定 |
| 计数排序 (Counting Sort) | O(n+k) | O(n+k) | O(n+k) | 稳定 |
| 快速排序 (Quick Sort) | O(n log n) | O(n²) | O(log n) - O(n) | 不稳定 |
| 归并排序 (Merge Sort) | O(n log n) | O(n log n) | O(n) | 稳定 |
| 堆排序 (Heap Sort) | O(n log n) | O(n log n) | O(1) | 不稳定 |
注:`n` 是元素个数,`d` 是最大元素的位数(或最大长度),`k` 是基数(如十进制为 10)。基数排序的时间复杂度严格来说是 O(d·(n+k)),因为内部通常用计数排序,但当 k 远小于 n 时,常简化为 O(d·n)。
LSD 会先看个位 (0, 5, 5, 0, 2, 4, 2, 6),MSD 会先看百位 (1, 0, 0, 0, 8, 0, 0, 0)。
LSD(Least Significant Digit)基数排序是最常用的基数排序形式。它从整数的**最低有效位**(即个位)开始,逐位向左(十位、百位...)进行排序。每一轮排序都利用一个**稳定的**排序算法(通常是计数排序的变体)来根据当前位的数字将元素分配到 0 到 9 这 10 个桶中,然后按桶的顺序(0 号桶到 9 号桶)将元素收集回原数组。这个过程重复进行,直到最高位也被处理完毕。
初始数组:
桶 (Buckets): (点击下方按钮开始演示)
MSD(Most Significant Digit)基数排序与 LSD 相反,它从**最高有效位**(最左边)开始进行排序。MSD 排序通常采用**递归**的方式。
假设我们对数组 `[170, 45, 75, 90, 802, 24, 2, 66]` 进行 MSD 排序(假设最大 3 位)。
第一轮 (百位):
这个过程会递归下去,直到所有子数组都被排序。
MSD 关注最高位进行首次划分。
基数排序的时间复杂度主要取决于三个因素:
每一轮排序(共 d 轮),都需要遍历 n 个元素进行分配,并遍历 k 个桶进行收集。如果使用计数排序作为内部排序,每轮的时间复杂度是 O(n+k)。因此,总时间复杂度为 O(d·(n+k))。
在很多情况下,k 是一个常数(如十进制 k=10)或者 k 相对于 n 较小(k ≪ n),此时复杂度可以简化为 O(d·n)。
关键点:基数排序的时间复杂度是**线性的**(相对于 n),前提是 d 不太大。如果 d 非常大(例如,数字非常大,d ≈ n),则性能可能不如 O(n log n) 的比较排序。
基数排序需要额外的存储空间:
因此,总的空间复杂度是 O(n + k)。
基数排序是**稳定**的排序算法。这是因为它依赖的内部排序(如计数排序)必须是稳定的。在每一轮按位排序时,如果两个元素当前位相同,它们在桶中的相对顺序以及收集回数组后的相对顺序,都与它们进入这一轮时的相对顺序保持一致。这保证了具有相同值的元素在排序后其原始的相对顺序不变。
上图示意了在 d 固定时,O(d·n) 相比 O(n log n) 的增长趋势。
| 优点 | 缺点 |
|---|---|
| ✅ 当 d 较小时,时间复杂度接近线性 O(n),非常快。 | ❌ 需要额外的 O(n+k) 空间,空间开销较大。 |
| ✅ 稳定排序。 | ❌ 对于位数 d 非常大的情况,性能可能不如 O(n log n) 算法。 |
| ✅ 实现相对直观(尤其是 LSD)。 | ❌ 不适用于无法按“位”拆分的数据(如浮点数需要特殊处理)。 |
| ✅ 适用于特定类型数据(整数、字符串)的大规模排序。 | ❌ 对基数 k 的选择会影响性能。 |
请选择你认为正确的答案。点击选项后会显示反馈。
进度: 1 / 5
测验完成!你的得分:0 / 5
请根据 LSD 基数排序的原理,实现一个 C++ 函数 `void radixSort(vector
要求:
// 函数签名
#include <vector>
#include <algorithm> // 用于 max_element
#include <cmath> // 用于 pow
using namespace std;
void radixSort(vector& arr);
标准的基数排序通常直接处理非负整数。请思考如何修改或扩展基数排序算法,使其能够正确地对包含**负数和正数**的整数数组进行排序。
提示:
// 思考如何修改 radixSort 或设计一个新的函数
void radixSortWithNegatives(vector& arr);