下载工作台
Go 编程实战

经典排序算法

试读上半部分 · 解锁后可读全文

第 14 章 · 经典排序、sort.Slice 与 Top N

本章目标:手写 冒泡、快速、归并 排序并理解复杂度;使用 sort.Slice / sort.SliceStable;实现 toolkit-go 慢请求 Top N商品价格排序;对照 java-dev ch17 算法章;为 ch15 毕业项目 准备 internal/sortalgo 包。

学时建议:4~5 小时(含 2.5 小时跟练)

前置:完成 go-dev ch13(testing 与 benchmark)。


14.1 场景说明:Top N 慢请求

运营日报只需展示 耗时最高的 N 条 path(如 N=10)。access 日志经 ch11 聚合后得到 []SlowRow;需按 avg_ms 或 max_ms 降序 取 Top N。

internal/sortalgo/
├── bubble.go      # 教学:O(n²)
├── quick.go       # 分治:平均 O(n log n)
├── merge.go       # 稳定:O(n log n)
├── topn.go        # 工程:sort.Slice + TopN
└── sort_test.go   # 表驱动 + benchmark
需求排序键稳定?
慢请求 Top NMaxMsAvgMs 降序
商品目录Price)升序同价按 Slug
已发布优先IsPublished 降序,再 Price是 → SliceStable

原则:工程 99% 用 sort 标准库;手写算法为理解复杂度与面试;数据量极大时用 heap Top K(选修)。


14.2 评价指标

指标含义
时间复杂度随 n 增长的操作次数(平均/最坏)
空间复杂度额外内存
稳定性相等 key 排序后相对顺序是否不变
符号直觉
O(n²)双重循环朴素排序
O(n log n)优秀比较排序下界
O(1)原地、无额外数组
算法平均最坏空间稳定
冒泡O(n²)O(n²)O(1)
快速O(n log n)O(n²)O(log n)
归并O(n log n)O(n log n)O(n)
sort.SliceO(n log n)O(n log n)O(log n)
sort.SliceStableO(n log n)O(n log n)O(n)

14.3 冒泡排序(教学)

// internal/sortalgo/bubble.go
package sortalgo

func BubbleSortInt(nums []int) {
    n := len(nums)
    for i := 0; i < n-1; i++ {
        swapped := false
        for j := 0; j < n-1-i; j++ {
            if nums[j] > nums[j+1] {
                nums[j], nums[j+1] = nums[j+1], nums[j]
                swapped = true
            }
        }
        if !swapped {
            return // 已有序,提前结束
        }
    }
}

相邻比较,较大元素「冒泡」到末尾;已有序时 O(n)


14.4 快速排序

// internal/sortalgo/quick.go
package sortalgo

func QuickSortInt(a []int) {
    if len(a) < 2 {
        return
    }
    quickSortRange(a, 0, len(a)-1)
}

func quickSortRange(a []int, lo, hi int) {
    if lo >= hi {
        return
    }
    p := partition(a, lo, hi)
    quickSortRange(a, lo, p-1)
    quickSortRange(a, p+1, hi)
}

func partition(a []int, lo, hi int) int {
    pivot := a[hi]
    i := lo
    for j := lo; j < hi; j++ {
        if a[j] < pivot {
            a[i], a[j] = a[j], a[i]
            i++
        }
    }
    a[i], a[hi] = a[hi], a[i]
    return i
}

最坏 O(n²):已有序 + 固定 pivot;工程可用 随机 pivot 或直接用 sort.Ints

mid 溢出(Java 对照):Go 的 lo + (hi-lo)/2 安全,避免 (lo+hi)/2 溢出(C/Java 老坑)。


14.5 归并排序

// internal/sortalgo/merge.go
package sortalgo

func MergeSortInt(a []int) []int {
    if len(a) <= 1 {
        return a
    }
    mid := len(a) / 2
    left := MergeSortInt(a[:mid])
    right := MergeSortInt(a[mid:])
    return mergeInt(left, right)
}

func mergeInt(left, right []int) []int {
    out := make([]int, 0, len(left)+len(right))
    i, j := 0, 0
    for i < len(left) && j < len(right) {
        if left[i] <= right[j] {
            out = append(out, left[i])
            i++
        } else {
            out = append(out, right[j])
            j++
        }
    }
    out = append(out, left[i:]...)
    out = append(out, right[j:]...)
    return out
}

稳定;适合 链表、外部大文件;空间 O(n)。


14.6 sort.Slice 与 SlowRow

// internal/sortalgo/topn.go
package sortalgo

import (
    "sort"

    "example.com/toolkit-go/internal/model"
    "example.com/toolkit-go/internal/report"
)

func SortSlowRowsDesc(rows []report.SlowRow) {
    sort.Slice(rows, func(i, j int) bool {
        if rows[i].MaxMs != rows[j].MaxMs {
            return rows[i].MaxMs > rows[j].MaxMs
        }
        return rows[i].Path < rows[j].Path // 次键字典序
    })
}

func TopNSlowRows(rows []report.SlowRow, n int) []report.SlowRow {
    if n <= 0 || len(rows) == 0 {
        return nil
    }
    SortSlowRowsDesc(rows)
    if n > len(rows) {
        n = len(rows)
    }
    // 拷贝避免修改 caller 的 slice 语义混乱
    out := make([]report.SlowRow, n)
    copy(out, rows[:n])
    return out
}

func SortProducts(products []model.Product) {
    sort.SliceStable(products, func(i, j int) bool {
        if products[i].Price != products[j].Price {
            return products[i].Price < products[j].Price
        }
        return products[i].Slug < products[j].Slug
    })
}

model.Product(ch05/ch11 复习):

type Product struct {
    Slug        string
    Name        string
    Price       int64  // 分
    IsPublished bool
}

14.7 Top N 与 heap(选修)

全排序 O(m log m),m 为慢 path 数;若 m 很大而 N 很小,用 小顶堆 维护 Top N:

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。