sort.Slice 覆盖 99% 场景,安全、快、防退化;手写快排易因空切片、边界错误等导致 panic 或逻辑错乱,partition 中不可直接用 len(arr)-1 作索引。

别手写快排,除非你清楚自己在绕过什么——sort.Slice 覆盖 99% 场景,安全、快、防退化;手写版本极易因切片边界、空切片、pivot 归位错误导致 panic 或逻辑错乱。
为什么 partition 函数里不能用 len(arr) - 1 当索引?
空切片时 len(arr) 是 0,len(arr) - 1 就是 -1,直接当索引用会 panic。真实递归中传入的子切片(如 arr[lo:hi])长度可能远小于原始切片,但 hi 才是右边界——必须用 hi - 1 或显式传入 right 下标,而不是无脑算 len(arr) - 1。
- 正确做法:函数签名带
low, high int参数,或用len(sub) > 0判空后再取末尾 - 常见错误:在
partition开头写pivot := arr[len(arr)-1],没检查len(arr) == 0 - 更隐蔽的坑:用
for j := 0; j ,当 <code>len(arr) == 1时循环不执行,pivot 没参与分区,最终位置错乱
用 append 拼左右子数组为什么永远排不对原切片?
append 在底层数组容量不足时会分配新数组,返回的新切片指向新内存,原切片不变——你看到的“排序结果”只是临时副本,原变量毫无变化。这不是算法错,是 Go 切片语义误解。
- 典型错误写法:
left := append([]int{}, ...); right := append([]int{}, ...); return append(append(left, pivot), right...) - 后果:原切片未被修改,后续调用
sort.Search查不到结果;GC 压力大;空间复杂度从O(1)暴涨到O(n²) - 正确路径:只用
arr[i], arr[j] = arr[j], arr[i]原地交换,所有递归都操作同一底层数组
sort.Slice 的三个硬约束你漏过哪个?
sort.Slice 看似简单,但 runtime panic 往往就卡在这三条上:
立即学习“go语言免费学习笔记(深入)”;
- 切片不能为
nil:必须提前判空,if data == nil || len(data) == 0 { return } - 比较函数必须满足严格弱序:对任意
i,less(i, i)必须返回false,否则行为未定义(比如写成return a[i].X 就违规) - 字段含
nil指针或空字符串时,less里必须显式处理,否则 panic;例如if a[i].Name == nil { return false }(把nil当最大值)
非要手写 quicksort,只保留这个最小可靠骨架
仅限算法题、教学、或嵌入式禁用标准库场景。以下版本已验证:空切片安全、pivot 归位正确、不新建底层数组、终止条件鲁棒。
func quicksort(arr []int) {
if len(arr) <= 1 {
return
}
p := partition(arr)
quicksort(arr[:p])
quicksort(arr[p+1:])
}
<p>func partition(arr []int) int {
n := len(arr)
pivot := arr[n-1]
i := 0
for j := 0; j < n-1; j++ {
if arr[j] <= pivot {
arr[i], arr[j] = arr[j], arr[i]
i++
}
}
arr[i], arr[n-1] = arr[n-1], arr[i]
return i
}注意:这个版本默认 pivot 取末尾,不随机——业务代码里真要防最坏情况,得加 rand.Seed 和 swap,但多数内部排序数据量小,没必要;真正关键的是别让 partition 返回错索引,否则递归区间崩塌。


















