一、扑克牌的故事:如何快速整理一副牌
小明拿到了一副打乱的扑克牌,需要按照 "花色 + 数字" 的顺序整理好。他想到了一个办法:
-
第一次分组:先按照花色(红桃、方块、黑桃、梅花)分成四堆
-
第二次分组:对每堆牌,再按照数字(A, 2-10, J, Q, K)进行排序
-
合并结果:按顺序合并所有牌堆
这就是基数排序的核心思想:按位分组,逐步细化。
二、基数排序的 Java 代码实现
java
public class RadixSort {
public static void sort(int[] array) {
if (array == null || array.length == 0) return;
// 1. 找出最大值,确定最大位数
int max = array[0];
for (int num : array) {
if (num > max) max = num;
}
// 2. 从个位到最高位,逐位排序
for (int exp = 1; max / exp > 0; exp *= 10) {
countingSortByDigit(array, exp);
}
}
// 按位进行计数排序(稳定排序)
private static void countingSortByDigit(int[] array, int exp) {
int n = array.length;
int[] output = new int[n];
int[] count = new int[10]; // 0-9共10个数字
// 1. 统计当前位的出现次数
for (int i = 0; i < n; i++) {
int digit = (array[i] / exp) % 10;
count[digit]++;
}
// 2. 计算前缀和,确定元素位置
for (int i = 1; i < 10; i++) {
count[i] += count[i - 1];
}
// 3. 从后向前遍历,保证排序稳定性
for (int i = n - 1; i >= 0; i--) {
int digit = (array[i] / exp) % 10;
output[count[digit] - 1] = array[i];
count[digit]--;
}
// 4. 复制回原数组
System.arraycopy(output, 0, array, 0, n);
}
}
三、基数排序的核心步骤解析
1. 确定排序位数
找出数组中的最大值,确定需要处理的最大位数:
java
// 示例数组:[170, 45, 75, 90, 802, 24, 2, 66]
max = 802 → 最大位数为3(百位)
2. 按位进行计数排序
从最低位(个位)开始,逐位进行稳定排序:
java
// 第一次排序(个位):
// 原数组:[170, 45, 75, 90, 802, 24, 2, 66]
// 按个位排序后:[170, 90, 802, 2, 24, 45, 75, 66]
// 第二次排序(十位):
// 按十位排序后:[802, 2, 24, 45, 66, 170, 75, 90]
// 第三次排序(百位):
// 按百位排序后:[2, 24, 45, 66, 75, 90, 170, 802] → 最终结果
3. 稳定排序的重要性
基数排序依赖稳定排序保证每一步的结果正确传递到下一步。例如:
java
// 第二次排序(十位)时,802和2的十位都是0
// 由于第一次排序后802在前,稳定排序会保持这个顺序
// 因此第二次排序后:[802, 2, ...],而不是[2, 802, ...]
四、基数排序的性能分析
-
时间复杂度:O(d × (n + k))
- d:最大位数
- n:元素个数
- k:每一位的取值范围(通常为 10)
-
空间复杂度:O(n + k)
-
稳定性:稳定
-
适用条件:
- 整数或可转换为整数的数据(如字符串按字符编码)
- 位数较少(d 较小)
五、基数排序的优化与变种
1. 处理负数
将所有数加上一个偏移量,转换为非负数:
java
// 示例:处理包含负数的数组 [-123, 45, -6, 789]
// 偏移量 = 123
// 转换后:[0, 168, 117, 912]
// 排序后再减去偏移量还原
2. 字符串排序
按字符编码逐位排序,从最低有效位(LSD)到最高有效位(MSD):
java
// 示例:排序 ["apple", "banana", "cherry"]
// 按最后一个字符排序:["cherry", "apple", "banana"]
// 按倒数第二个字符排序:["apple", "banana", "cherry"]
// ...
六、基数排序 vs 其他排序算法
| 排序算法 | 时间复杂度 | 空间复杂度 | 稳定性 | 适用场景 |
|---|---|---|---|---|
| 基数排序 | O(d × (n + k)) | O(n + k) | 稳定 | 整数或字符串排序 |
| 快速排序 | O(n log n) | O(log n) | 不稳定 | 通用场景 |
| 归并排序 | O(n log n) | O(n) | 稳定 | 大规模数据、链表排序 |
| 计数排序 | O(n + k) | O(k) | 稳定 | 数据范围小的整数排序 |
七、总结:基数排序的核心思想
基数排序的本质是 "逐位分解,按位排序":
-
按位分组:从最低位到最高位,依次对每一位进行排序
-
稳定传递:依赖稳定排序保证前一步的结果不被破坏
就像整理扑克牌,先按花色分堆,再按数字细化。基数排序特别适合处理位数较少的整数或字符串排序,效率高于基于比较的排序算法。但它需要额外空间存储临时数组,且不适用于无法转换为固定位数的数据类型。