首选 sort.Slice,因其安全、高效、防退化;手写快排易出错,仅适用于算法题或禁用标准库场景。

别手写快排,除非你明确知道为什么必须绕过 sort.Slice——它已覆盖 99% 的真实场景,且更安全、更快、自带防退化;手写版本极易因空切片、边界计算、pivot 归位错误导致 panic 或逻辑错乱。
为什么 sort.Slice 是首选,而不是自己实现
标准库的 sort.Slice 在 Go 1.8+ 中引入,专为任意切片类型设计,无需定义新类型或实现 sort.Interface。它底层使用 pdqsort(pattern-defeating-quicksort),混合了快排、插入排序和堆排序,能自动应对已排序、逆序、大量重复等最坏输入,避免 O(n²) 退化。
常见误判是“我要按结构体某个字段排序,得手写”。其实只需:
sort.Slice(products, func(i, j int) bool {
return products[i].Price <p>注意三点硬约束:</p>
-
products不能为nil,否则运行时 panic;建议前置检查:if len(products) == 0 { return } - 比较函数中
less(i, i)必须返回false,比如写成return a[i].X 就违规 - 若字段含
nil指针(如*string),需显式处理:if a[i].Name == nil { return false }(把nil当最大值)
partition 函数里为什么不能直接用 len(arr) - 1 当索引
空切片时 len(arr) 是 0,len(arr) - 1 就是 -1,直接当索引用会 panic。而真实递归中传入的是子切片(如 arr[lo:hi]),其长度远小于原始切片,但右边界是 hi - 1,不是 len(arr) - 1。
典型错误写法:
func partition(arr []int) int {
pivot := arr[len(arr)-1] // 空切片时 panic!
// ...
}
正确做法是显式传入区间边界:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 函数签名带
low, high int参数,用arr[high]取 pivot - 或先判空:
if len(arr) == 0 { return 0 },再取末尾 - 终止条件必须是
low >= high,而非low >= len(arr)(后者第一次就跳过整个排序)
手写快排时用 append 拼左右子数组为什么永远排不对原切片
Go 切片是引用类型,但 append 在底层数组容量不足时会分配新数组,返回的新切片指向新内存,原切片不变。你看到的“排序结果”只是临时副本,原变量毫无变化。
错误示例:
arr
后果严重:
- 原切片未被修改,后续调用
sort.Search查不到结果 - 每次递归都分配新底层数组,空间复杂度从 O(1) 暴涨到 O(n²)
- GC 压力大,性能差
正确路径只用原地交换:arr[i], arr[j] = arr[j], arr[i],所有递归操作同一底层数组。
非要手写时,只保留这个最小可靠骨架
仅限算法题、教学演示或嵌入式禁用标准库场景。以下版本已验证:空切片安全、pivot 归位正确、不新建底层数组、终止条件鲁棒。
func quickSort(arr []int, low, high int) {
if low >= high {
return
}
pivotIndex := partition(arr, low, high)
quickSort(arr, low, pivotIndex-1)
quickSort(arr, pivotIndex+1, high)
}
<p>func partition(arr []int, low, high int) int {
pivot := arr[high]
i := low - 1
for j := low; j </p>
关键细节:pivot 选末尾不是因为它最优,而是边界最清晰;i 初始化为 low - 1 是为了统一处理所有元素;返回前必须确保 pivot 落在 i+1 位置,否则递归区间会越界或漏排。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










