摘要:基数排序(Radix Sort)是一种基于“分配-收集”思想的非比较排序算法,适用于整数排序。本文详细讲解其原理、流程、复杂度分析,辅以习题解析、表格总结及完整C代码实现。
一、算法思想与流程
核心思想:将整数按位切割(个位、十位等),依次对每一位进行排序(从低位到高位),最终使整体有序。排序过程通过“分配”到桶(Bucket)再“收集”回数组实现。
关键点:
- LSD(最低位优先) :从最低位开始排序(如个位→十位→百位),确保高位权重更高时排序正确 。
- 桶的使用:每个位取值范围为
0-9
(十进制),需10个桶(队列/链表)存放数据 。
算法流程:
- 初始化:
- 确定最大位数
d
(如384
的d=3
)。 - 创建10个空桶(对应数字0-9)。
- 确定最大位数
- 分配-收集(循环
d
次):- 分配:遍历数组,根据当前位的值将元素放入对应桶中(如个位为3放入桶3)。
- 收集:按桶号顺序(0→9)将元素取出放回原数组。
- 更新位数至下一位(如个位→十位)。
- 终止:完成最高位排序后,数组有序。
示例演示:
数组 [170, 45, 2, 789]
排序过程:
- 个位分配:桶0:170, 桶2:2, 桶5:45, 桶9:789 → 收集为
[170, 2, 45, 789]
。 - 十位分配:桶7:170, 桶4:45, 桶8:789, 桶2:2 → 收集为
[2, 45, 170, 789]
。 - 百位分配:桶0:2,45,170, 桶7:789 → 最终有序 。
二、习题解析
问题1:为什么必须从最低位开始排序?
解析:若从高位开始,高位相同的数仍需按低位排序,导致额外操作。LSD保证高位权重更高时排序一次完成,效率更高 。
问题2:如何计算最大位数 d
?
解析:
int GetMaxDigit(int arr[], int n) {
int max_val = arr[0];
for (int i = 1; i < n; i++)
if (arr[i] > max_val) max_val = arr[i];
int d = 0;
while (max_val) { d++; max_val /= 10; } // 整除计数
return d;
}
示例:数组 [100, 5, 45]
,max_val=100
→ d=3
。
三、表格总结
特性 | 说明 |
---|---|
时间复杂度 | O(d · (n + k))(d:位数, k:基数10) |
空间复杂度 | O(n + k)(k个桶) |
稳定性 | 稳定(相同值保持原序) |
适用场景 | 整数排序,尤其位数少且范围集中 |
限制 | 不适用于浮点数或字符串 |
注:d 较小且 n 较大时效率接近 O(n) 。
四、记忆公式探索
- 桶索引计算:
- 第
i
位(从0起,0=个位)的值:int digit = (num / (int)pow(10, i)) % 10; // 如384的十位: (384/10)%10=8
- 第
- 收集顺序:桶号从小到大(0→9)保证升序 。
- 代码执行流程:
五、C语言完整代码
(1)源代码:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define RADIX 10 // 十进制基数
typedef struct Node {
int data;
struct Node *next;
} Node;
// 获取最大位数
int GetMaxDigit(int arr[], int n) {
int max_val = arr[0];
for (int i = 1; i < n; i++)
if (arr[i] > max_val) max_val = arr[i];
int d = 0;
while (max_val) { d++; max_val /= 10; }
return d;
}
// 基数排序主函数
void RadixSort(int arr[], int n) {
int d = GetMaxDigit(arr, n);
Node *buckets[RADIX], *tails[RADIX]; // 桶头尾指针数组
for (int i = 0; i < d; i++) {
// 初始化桶
for (int j = 0; j < RADIX; j++) {
buckets[j] = NULL;
tails[j] = NULL;
}
// 分配:按当前位放入桶
for (int j = 0; j < n; j++) {
int digit = (arr[j] / (int)pow(10, i)) % 10;
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = arr[j];
newNode->next = NULL;
if (!buckets[digit]) {
buckets[digit] = newNode;
tails[digit] = newNode;
} else {
tails[digit]->next = newNode;
tails[digit] = newNode;
}
}
// 收集:按桶号0→9取回数组
int idx = 0;
for (int j = 0; j < RADIX; j++) {
Node *cur = buckets[j];
while (cur) {
arr[idx++] = cur->data;
Node *tmp = cur;
cur = cur->next;
free(tmp); // 释放节点
}
}
}
}
// 测试代码
int main() {
int arr[] = {170, 45, 2, 789, 53, 100, 0};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
RadixSort(arr, n);
printf("\n排序后: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
return 0;
}
(2)代码讲解:
①数据结构定义
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define RADIX 10 // 十进制基数(桶的数量)
typedef struct Node {
int data; // 存储原始数据
struct Node *next; // 链表指针
} Node;
- 核心目的:使用链表动态管理桶,避免固定大小数组的空间浪费 。
- 关键点:
RADIX 10
:因十进制数每位取值范围为0-9,故需10个桶。- 链表节点:动态存储数据,适应不同规模的数据集 。
②辅助函数解析
1. 计算最大位数 GetMaxDigit
int GetMaxDigit(int arr[], int n) {
int max_val = arr[0];
for (int i = 1; i < n; i++)
if (arr[i] > max_val) max_val = arr[i];
int d = 0;
while (max_val) { d++; max_val /= 10; } // 整除计数
return d;
}
- 作用:确定排序轮次(如384的位数d=3)。
- 实现逻辑:
- 遍历数组找到最大值
max_val
。 - 通过
max_val /= 10
逐位消减,统计位数d
。
- 遍历数组找到最大值
- 注意:若
max_val=0
时返回d=0,需额外处理(代码中已确保数组非空)。
2. 提取特定位的数字 GetDigit
int digit = (arr[j] / (int)pow(10, i)) % 10;
- 原理:
例如取384的十位(i=1
):
(384 / 10^1) = 38
→38 % 10 = 8
。 - 优化替代:可用循环除法避免浮点运算(见)。
③基数排序核心函数 RadixSort
1. 初始化桶
Node *buckets[RADIX], *tails[RADIX];
for (int j = 0; j < RADIX; j++) {
buckets[j] = NULL; // 桶头指针
tails[j] = NULL; // 桶尾指针(提高链表追加效率)
}
- 桶设计:每个桶是独立链表,
buckets[]
存储头节点,tails[]
指向尾节点以加速插入 。
2. 分配过程(Distribute)
for (int j = 0; j < n; j++) {
int digit = (arr[j] / (int)pow(10, i)) % 10; // 计算当前位值
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = arr[j];
newNode->next = NULL;
// 插入链表尾部
if (!buckets[digit]) {
buckets[digit] = newNode;
tails[digit] = newNode;
} else {
tails[digit]->next = newNode;
tails[digit] = newNode;
}
}
- 关键操作:
- 根据当前位
digit
选择桶号(0-9)。 - 尾插法维护链表顺序,保证稳定性(相同位值保持原序)。
- 根据当前位
3. 收集过程(Collect)
int idx = 0;
for (int j = 0; j < RADIX; j++) { // 按桶号0→9顺序收集
Node *cur = buckets[j];
while (cur) {
arr[idx++] = cur->data; // 放回原数组
Node *tmp = cur;
cur = cur->next;
free(tmp); // 释放节点内存
}
}
- 核心逻辑:
- 按桶号升序(0→9)遍历,保证排序结果升序。
- 释放节点避免内存泄漏。
- 稳定性体现:同桶内元素保持插入顺序 。
(3)输出结果:
排序前: 170 45 2 789 53 100 0
排序后: 0 2 45 53 100 170 789
六、总结
基数排序以空间换时间,在整数排序中效率优异,尤其适合位数少的场景(如手机号排序)。核心在于 LSD策略 和 桶的分配-收集,注意避免浮点数使用。理解位值计算和桶操作即可掌握其精髓 。