基数排序:非比较排序的高效实现

摘要:基数排序(Radix Sort)是一种基于“分配-收集”思想的非比较排序算法,适用于整数排序。本文详细讲解其原理、流程、复杂度分析,辅以习题解析、表格总结及完整C代码实现。


一、算法思想与流程

核心思想:将整数按位切割(个位、十位等),依次对每一位进行排序(从低位到高位),最终使整体有序。排序过程通过“分配”到桶(Bucket)再“收集”回数组实现。
关键点

  1. LSD(最低位优先) :从最低位开始排序(如个位→十位→百位),确保高位权重更高时排序正确 。
  2. 桶的使用:每个位取值范围为 0-9(十进制),需10个桶(队列/链表)存放数据 。

算法流程

  1. 初始化
    • 确定最大位数 d(如 384 的 d=3)。
    • 创建10个空桶(对应数字0-9)。
  2. 分配-收集(循环 d 次)
    • 分配:遍历数组,根据当前位的值将元素放入对应桶中(如个位为3放入桶3)。
    • 收集:按桶号顺序(0→9)将元素取出放回原数组。
    • 更新位数至下一位(如个位→十位)。
  3. 终止:完成最高位排序后,数组有序。

示例演示
数组 [170, 45, 2, 789] 排序过程:

  1. 个位分配:桶0:170, 桶2:2, 桶5:45, 桶9:789 → 收集为 [170, 2, 45, 789]
  2. 十位分配:桶7:170, 桶4:45, 桶8:789, 桶2:2 → 收集为 [2, 45, 170, 789]
  3. 百位分配:桶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) 。


四、记忆公式探索

  1. 桶索引计算
    • 第 i 位(从0起,0=个位)的值:
      int digit = (num / (int)pow(10, i)) % 10; // 如384的十位: (384/10)%10=8  
      
  2. 收集顺序:桶号从小到大(0→9)保证升序 。
  3. 代码执行流程:


五、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策略 和 桶的分配-收集,注意避免浮点数使用。理解位值计算和桶操作即可掌握其精髓 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小李独爱秋

你的鼓励将是我加更的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值