不该自己封装qsort风格快排——Go标准库sort.Slice和sort.Ints等已覆盖全部场景,更安全、防退化、原地修改、无需手写边界逻辑。

别封装 qsort 风格的快速排序函数——Go 标准库的 sort.Slice 和 sort.Ints 等已覆盖全部常见场景,且更安全、防退化、原地修改、无需手写边界逻辑。
为什么你不该自己写 quicksort 函数
手写快排在 Go 里极易触发三类真实崩溃或静默错误:
-
len(arr) - 1在空切片时为-1,直接当索引用会 panic - 分区循环中
j越界到len(arr),或漏掉 pivot 归位,导致子数组错乱 - 用
append(left, pivot, right...)拼接,实际分配新底层数组,原切片完全没变
这些不是算法问题,而是 Go 切片语义和内存模型的硬约束。标准库已用 pdqsort(混合快排/堆排/插入排序)规避所有退化路径,你重写的版本大概率更慢、更脆。
用 sort.Slice 实现任意结构体多字段排序
这是最常用、最灵活、也最容易写对的方式。它不要求定义新类型,只传一个 func(i, j int) bool:
立即学习“go语言免费学习笔记(深入)”;
sort.Slice(users, func(i, j int) bool {
a, b := users[i], users[j]
if a.City == nil && b.City != nil {
return false // nil 排最后
}
if a.City != nil && b.City == nil {
return true
}
if *a.City != *b.City {
return *a.City < *b.City // City 升序
}
return a.Age < b.Age // Age 升序
})
注意点:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 必须提前检查
len(users) > 0,否则users[i]在空切片时 panic -
less(i, i)必须返回false,否则行为未定义 - 比较指针字段(如
*string)前务必判nil,否则解引用 panic
基础类型排序优先用 sort.Ints / sort.Strings
它们是专用函数,编译期绑定,零运行时开销,且自带防错:
-
sort.Ints只接受[]int,对[]int64或自定义别名直接编译报错,不让你误用 - 所有快捷函数都原地修改,不返回新切片,语义明确
- 底层是
pdqsort,对已部分有序、全相同、逆序等退化输入自动切换策略
示例:
nums := []int{3, 1, 4, 1, 5}
sort.Ints(nums) // 直接改 nums,返回 void
fmt.Println(nums) // [1 1 3 4 5]
非要手写?只保留最简 partition + 递归调用
仅限算法题、教学演示或禁用标准库的嵌入式环境。以下版本已验证边界安全:
func quicksort(arr []int) {
if len(arr) <= 1 {
return
}
pivot := partition(arr)
quicksort(arr[:pivot])
quicksort(arr[pivot+1:])
}
<p>func partition(arr []int) int {
n := len(arr)
pivotVal := arr[n-1]
i := 0
for j := 0; j < n-1; j++ {
if arr[j] <= pivotVal {
arr[i], arr[j] = arr[j], arr[i]
i++
}
}
arr[i], arr[n-1] = arr[n-1], arr[i]
return i
}
关键细节:
- 基准选末尾元素,避免
arr[0]在空切片 panic - 循环范围是
0..n-2,确保不越界也不动 pivot 位置 - 归位后返回
i是 pivot 最终下标,子调用用arr[:i]和arr[i+1:],严格符合 Go 切片左闭右开语义
真正的难点不在算法本身,而在于:每次切片传递都隐含底层数组共享,partition 过程中若读到切片外的脏数据(比如原底层数组后面有未初始化值),结果就不可控——这正是标准库用 pdqsort 加额外边界检查的原因。

















