欢迎光临
我们一直在努力

go语言:实现msd 基数排序算法(附带源码)

一、项目背景详细介绍

在算法学习与工程实践中,排序算法始终是最核心的基础能力之一。从最经典的冒泡排序、选择排序、插入排序,到时间复杂度更优的快速排序、归并排序、堆排序,再到线性时间复杂度的计数排序与基数排序,不同算法适用于不同的数据场景。

在工程实践中,我们常常会遇到以下类型的数据排序需求:

  • 字符串字典序排序

  • 固定位数整数排序

  • 电话号码排序

  • 身份证号排序

  • 日志编号排序

  • 大规模数据排序(可利用非比较排序优化)

传统的比较排序(如快速排序)理论下界为 O(n log n),这是基于比较决策树模型推导出来的。然而,对于某些特殊结构的数据(例如定长字符串、定长整数),我们可以绕过比较模型,使用非比较排序算法实现 O(n) 的时间复杂度。

基数排序(Radix Sort)就是这样一种算法。

基数排序主要分为两种实现方式:

  • LSD(Least Significant Digit)最低位优先排序

  • MSD(Most Significant Digit)最高位优先排序

  • 本项目将重点讲解:

    ✅ 使用 Go 语言实现 MSD 基数排序算法
    ✅ 支持字符串数组排序
    ✅ 支持整数数组排序
    ✅ 提供完整可运行代码
    ✅ 提供完整原理解读


    二、项目需求详细介绍

    本项目的目标如下:

    功能需求

  • 实现字符串数组的 MSD 基数排序

  • 实现整数数组的 MSD 基数排序

  • 支持不同长度字符串排序

  • 支持递归分组排序

  • 时间复杂度接近 O(n)

  • 稳定排序

  • 技术要求

    • 使用 Go 语言实现

    • 代码结构清晰

    • 所有代码放入单一代码块

    • 不拆分多个代码块

    • 使用注释区分不同文件

    • 每个函数必须有详细注释

    • 提供完整 main 函数测试

    性能目标

    • 时间复杂度:O(n * W)

      • W 为最大字符长度

    • 空间复杂度:O(n + R)

      • R 为字符集大小


    三、相关技术详细介绍

    1️⃣ 基数排序(Radix Sort)

    基数排序是一种非比较排序算法。

    它的核心思想:

    按位(digit)对数据进行分组排序

    例如排序字符串:

    cat
    dog
    apple
    banana

    MSD排序方式:

    • 第1位字符排序

    • 分组

    • 对每组递归排序第2位

    • 直到所有字符处理完


    2️⃣ MSD(Most Significant Digit)

    MSD 是:

    从最高位开始排序

    例如字符串排序:

    abc
    aac
    bbb

    步骤:

  • 按第0位字符排序

  • 分组

  • 每组递归处理第1位

  • 直到字符处理完

  • 特点:

    • 适合字符串排序

    • 适合前缀差异大的数据

    • 可以提前终止


    3️⃣ 与 LSD 的区别

    对比项MSDLSD
    排序方向 从高位到低位 从低位到高位
    是否递归
    是否适合字符串 非常适合 适合定长
    实现难度 较复杂 较简单

    4️⃣ 字符集大小(R)

    • ASCII:256

    • 小写字母:26

    • 数字:10

    本实现采用 ASCII 256 作为通用字符集。


    四、实现思路详细介绍

    一、字符串 MSD 排序整体流程

    假设数组为:

    ["banana", "apple", "orange", "grape"]

    步骤:

  • 从第0位字符排序

  • 统计频率

  • 计算索引

  • 分类写入辅助数组

  • 拷贝回原数组

  • 对每个字符桶递归排序


  • 二、核心函数设计

    1️⃣ charAt(s, d)

    作用:

    • 获取字符串第 d 位字符

    • 若超出长度返回 -1

    2️⃣ msdSort(a, lo, hi, d)

    参数说明:

    • a: 原数组

    • lo: 左边界

    • hi: 右边界

    • d: 当前处理字符位

    流程:

  • 若区间长度小于等于1则返回

  • 统计字符频率

  • 构建前缀和

  • 分类

  • 拷贝

  • 递归处理子区间


  • 三、整数 MSD 排序实现思路

    整数排序思路:

  • 将整数转为字符串

  • 使用字符串 MSD 排序

  • 再转回整数

  • 或者:

    按字节排序(更底层)

    本项目使用字符串转换方式,便于教学理解。


    五、完整实现代码

    // ===========================
    // 文件名:main.go
    // ===========================

    package main

    import (
    "fmt"
    "strconv"
    )

    // ===========================
    // 常量定义
    // ===========================

    // R 表示字符集大小,这里使用 ASCII 256
    const R = 256

    // ===========================
    // 工具函数区域
    // ===========================

    // charAt 获取字符串 s 的第 d 个字符
    // 如果 d 超出字符串长度,则返回 -1
    // 这样可以保证较短字符串优先排序
    func charAt(s string, d int) int {
    if d < len(s) {
    return int(s[d])
    }
    return -1
    }

    // ===========================
    // 字符串 MSD 排序实现
    // ===========================

    // MSDStringSort 对字符串数组进行 MSD 基数排序
    func MSDStringSort(a []string) {
    n := len(a)
    aux := make([]string, n)
    msdSort(a, aux, 0, n-1, 0)
    }

    // msdSort 核心递归函数
    // a: 原数组
    // aux: 辅助数组
    // lo: 当前排序起始索引
    // hi: 当前排序结束索引
    // d: 当前处理的字符位
    func msdSort(a []string, aux []string, lo, hi, d int) {

    // 递归终止条件
    if hi <= lo {
    return
    }

    // 创建频率统计数组
    count := make([]int, R+2)

    // 第一步:统计频率
    for i := lo; i <= hi; i++ {
    c := charAt(a[i], d)
    count[c+2]++
    }

    // 第二步:计算前缀和(索引)
    for r := 0; r < R+1; r++ {
    count[r+1] += count[r]
    }

    // 第三步:数据分类
    for i := lo; i <= hi; i++ {
    c := charAt(a[i], d)
    aux[count[c+1]] = a[i]
    count[c+1]++
    }

    // 第四步:拷贝回原数组
    for i := lo; i <= hi; i++ {
    a[i] = aux[i-lo]
    }

    // 第五步:递归处理每个字符桶
    for r := 0; r < R; r++ {
    msdSort(a, aux, lo+count[r], lo+count[r+1]-1, d+1)
    }
    }

    // ===========================
    // 整数 MSD 排序实现
    // ===========================

    // MSDIntSort 对整数数组进行 MSD 基数排序
    func MSDIntSort(arr []int) {

    // 将整数转为字符串
    strArr := make([]string, len(arr))
    for i, v := range arr {
    strArr[i] = strconv.Itoa(v)
    }

    // 调用字符串排序
    MSDStringSort(strArr)

    // 转回整数
    for i, v := range strArr {
    num, _ := strconv.Atoi(v)
    arr[i] = num
    }
    }

    // ===========================
    // 测试函数
    // ===========================

    func main() {

    fmt.Println("===== 字符串 MSD 排序测试 =====")
    strArr := []string{
    "banana",
    "apple",
    "orange",
    "grape",
    "pear",
    "peach",
    "apricot",
    }

    fmt.Println("排序前:", strArr)
    MSDStringSort(strArr)
    fmt.Println("排序后:", strArr)

    fmt.Println("\\n===== 整数 MSD 排序测试 =====")
    intArr := []int{
    329,
    457,
    657,
    839,
    436,
    720,
    355,
    }

    fmt.Println("排序前:", intArr)
    MSDIntSort(intArr)
    fmt.Println("排序后:", intArr)
    }

    六、代码详细解读(仅解读方法作用)

    charAt

    用于获取字符串指定位置字符。

    若字符不存在,返回 -1,用于保证短字符串优先。


    MSDStringSort

    字符串排序入口函数:

    • 创建辅助数组

    • 调用核心递归函数


    msdSort

    核心递归函数:

  • 判断递归终止条件

  • 统计字符频率

  • 计算前缀和

  • 分类写入辅助数组

  • 拷贝回原数组

  • 递归处理子区间


  • MSDIntSort

    整数排序函数:

  • 整数转字符串

  • 调用字符串排序

  • 转回整数


  • main

    测试:

    • 字符串排序

    • 整数排序


    七、项目详细总结

    本项目完整实现了:

    • Go语言 MSD 基数排序

    • 字符串排序

    • 整数排序

    • 递归分桶排序

    • 非比较排序

    • 稳定排序

    优点:

    • 时间复杂度低

    • 适合大规模字符串排序

    • 可提前终止递归

    缺点:

    • 占用额外空间

    • 实现复杂

    • 对字符集依赖较强


    八、项目常见问题及解答

    Q1:为什么 count 数组是 R+2?

    因为:

    • -1 也要占一个位置

    • 需要额外空间计算前缀和


    Q2:为什么短字符串排在前面?

    因为 charAt 返回 -1,映射到最前面桶。


    Q3:时间复杂度是多少?

    O(n * W)

    W 为最大字符串长度。


    Q4:MSD 为什么要递归?

    因为每一位字符分组后,需要对每组继续排序下一位。


    Q5:可以优化吗?

    可以加入:

    • 小数组使用插入排序优化

    • 限制递归深度

    • 使用 byte 数组代替 string


    九、扩展方向与性能优化

    1️⃣ 小数组切换插入排序

    当 hi-lo 小于某个阈值:

    使用插入排序可提升性能。


    2️⃣ 并发优化

    可以使用 goroutine 对不同桶并行排序。


    3️⃣ 支持 Unicode

    当前为 ASCII。

    可以改为 rune 版本。


    4️⃣ 使用字节数组优化

    减少字符串访问开销。


    5️⃣ 用于搜索引擎排序

    • 单词字典排序

    • URL排序

    • 日志排序


    结语

    本篇文章系统讲解了:

    • MSD 基数排序原理

    • Go语言完整实现

    • 递归分桶思想

    • 复杂度分析

    • 性能优化方向

    如果你能完全理解本实现,那么你已经掌握:

    非比较排序的核心思想
    字符串排序的高级实现
    递归分治排序结构

    这是算法能力进阶的重要一步。

    赞(0)
    未经允许不得转载:171主机测评 » go语言:实现msd 基数排序算法(附带源码)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址