ZetCode

Go 基数排序

最后修改于 2025年3月8日

本教程将在 Go 中实现并解释基数排序算法,重点关注高效地对数值和文本数据进行排序。

一个算法是为了解决问题或执行任务而精心设计的精确步骤序列。它是编程的核心概念,驱动计算解决方案。

排序涉及将数据安排成预定义的顺序,例如升序或降序。它提高了数据访问速度,并支持搜索引擎等应用程序中的分析。

常用排序算法

以下是一些著名的排序算法

基数排序

基数排序是一种非比较排序算法,它通过处理数据中的每个数字或字符来排序,从最低有效位到最高有效位。它在处理整数和固定长度字符串方面表现出色。

与快速排序等基于比较的排序不同,基数排序使用计数来将元素分发到存储桶中,在特定条件下提供线性时间复杂度。

基数排序示例

这是一个 Go 整数基数排序实现。

radix_sort.go
package main

import "fmt"

func countingSort(arr []int, exp int) {
    n := len(arr)
    output := make([]int, n)
    count := make([]int, 10)

    for i := 0; i < n; i++ {
        index := arr[i] / exp
        count[index%10]++
    }

    for i := 1; i < 10; i++ {
        count[i] += count[i-1]
    }

    for i := n - 1; i >= 0; i-- {
        index := arr[i] / exp
        output[count[index%10]-1] = arr[i]
        count[index%10]--
    }

    for i := 0; i < n; i++ {
        arr[i] = output[i]
    }
}

func radixSort(arr []int) {
    maxNum := arr[0]
    for _, num := range arr {
        if num > maxNum {
            maxNum = num
        }
    }
    for exp := 1; maxNum/exp > 0; exp *= 10 {
        countingSort(arr, exp)
    }
}

func main() {
    arr := []int{170, 45, 75, 90, 802, 24, 2, 66}
    radixSort(arr)
    fmt.Println("Sorted array:", arr)
}

此代码使用 `countingSort` 作为辅助函数,按每个数字进行排序,从最低有效位开始。`exp` 变量跟踪当前数字位(1、10、100 等)。

`radixSort` 函数查找最大数字以确定要处理的位数。它就地修改数组,有效地将其按升序排序。

$ go run radix_sort.go
Sorted array: [2 24 45 66 75 90 170 802]

对文本数据进行排序

基数排序也可以对字符串进行排序。下面是一个对字符串进行升序和降序排序的示例。

radix_sort_strings.go
package main

import "fmt"

func countingSortStrings(arr []string, index int) {
    n := len(arr)
    output := make([]string, n)
    count := make([]int, 256)

    for i := 0; i < n; i++ {
        char := byte(0)
        if index < len(arr[i]) {
            char = arr[i][index]
        }
        count[char]++
    }

    for i := 1; i < 256; i++ {
        count[i] += count[i-1]
    }

    for i := n - 1; i >= 0; i-- {
        char := byte(0)
        if index < len(arr[i]) {
            char = arr[i][index]
        }
        output[count[char]-1] = arr[i]
        count[char]--
    }

    for i := 0; i < n; i++ {
        arr[i] = output[i]
    }
}

func radixSortStrings(arr []string, reverse bool) {
    maxLen := 0
    for _, s := range arr {
        if len(s) > maxLen {
            maxLen = len(s)
        }
    }
    for i := maxLen - 1; i >= 0; i-- {
        countingSortStrings(arr, i)
    }
    if reverse {
        for i, j := 0, len(arr)-1; i < j; i, j = i+1, j-1 {
            arr[i], arr[j] = arr[j], arr[i]
        }
    }
}

func main() {
    arr := []string{"apple", "banana", "kiwi", "mango", "cherry"}
    radixSortStrings(arr, false)
    fmt.Println("Ascending order:", arr)

    radixSortStrings(arr, true)
    fmt.Println("Descending order:", arr)
}

`countingSortStrings` 函数根据特定的字符位置进行排序,使用 256 个槽的计数数组来处理 ASCII 字符。它隐式地用空字节填充较短的字符串。

`radixSortStrings` 函数从右到左处理字符,以确保正确的字典顺序。`reverse` 标志在排序后交换元素以实现降序。

这对于在需要文本数据组织的 Go 应用程序中对名称、ID 或标签进行排序非常实用。

$ go run radix_sort_strings.go
Ascending order: [apple banana cherry kiwi mango]
Descending order: [mango kiwi cherry banana apple]

基数排序与快速排序的比较

基数排序在处理固定大小的数据(如整数或字符串)时表现出色,其时间复杂度为 O(nk),其中 k 是数字或字符的数量。快速排序的平均时间复杂度为 O(n log n),用途更广。

下面的基准测试比较了它们在 Go 中处理大型整数数据集时的性能,突出了它们的优势。

radix_vs_quick.go
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func countingSort(arr []int, exp int) {
    n := len(arr)
    output := make([]int, n)
    count := make([]int, 10)

    for i := 0; i < n; i++ {
        index := arr[i] / exp
        count[index%10]++
    }

    for i := 1; i < 10; i++ {
        count[i] += count[i-1]
    }

    for i := n - 1; i >= 0; i-- {
        index := arr[i] / exp
        output[count[index%10]-1] = arr[i]
        count[index%10]--
    }

    for i := 0; i < n; i++ {
        arr[i] = output[i]
    }
}

func radixSort(arr []int) {
    maxNum := arr[0]
    for _, num := range arr {
        if num > maxNum {
            maxNum = num
        }
    }
    for exp := 1; maxNum/exp > 0; exp *= 10 {
        countingSort(arr, exp)
    }
}

func quickSort(arr []int) []int {
    if len(arr) <= 1 {
        return arr
    }
    pivot := arr[len(arr)/2]
    left := []int{}
    middle := []int{}
    right := []int{}
    for _, x := range arr {
        if x < pivot {
            left = append(left, x)
        } else if x == pivot {
            middle = append(middle, x)
        } else {
            right = append(right, x)
        }
    }
    left = quickSort(left)
    right = quickSort(right)
    return append(append(left, middle...), right...)
}

func main() {
    rand.Seed(time.Now().UnixNano())
    arr := make([]int, 10000)
    for i := range arr {
        arr[i] = rand.Intn(10000)
    }

    radixData := make([]int, len(arr))
    copy(radixData, arr)
    start := time.Now()
    radixSort(radixData)
    radixTime := time.Since(start)

    quickData := make([]int, len(arr))
    copy(quickData, arr)
    start = time.Now()
    quickSort(quickData)
    quickTime := time.Since(start)

    fmt.Printf("Radix Sort time: %.6f seconds\n", radixTime.Seconds())
    fmt.Printf("Quick Sort time: %.6f seconds\n", quickTime.Seconds())
}

此基准测试创建了 10,000 个随机整数。`radixSort` 使用基于数字的计数进行就地排序,而 `quickSort` 通过递归地划分数据来返回一个新的切片。

对于数字范围较小的整数,基数排序通常优于快速排序,但快速排序更能适应各种数据类型。结果各不相同,但在此数据集上,基数排序通常略胜一筹。

来源

Go sort 包文档

本教程在 Go 中实现了基数排序,涵盖了数字和字符串,并与快速排序进行了比较以获取性能见解。

作者

我的名字是 Jan Bodnar,我是一名热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。到目前为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程