partition函数必须显式接收low、high参数:空切片时len(arr)-1会越界panic;子切片arr[lo:hi]右边界为hi-1,非len(arr)-1;调用方须保证low≤high。

使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
partition 函数必须带 low/high 参数,不能依赖 len(arr)
直接写 partition(arr []int) 然后用 arr[len(arr)-1] 取 pivot 是高危操作:空切片时 len(arr) 为 0,len(arr)-1 就是 -1,下标 panic 立刻发生。真实递归中传入的总是子切片(如 arr[lo:hi]),它的长度和原始切片无关,但右边界是 hi-1,不是 len(arr)-1。
- 函数签名必须显式接收
low, high int,且调用方保证low - 取 pivot 用
arr[high]最安全——high是有效索引,前提是调用前已校验区间非空 - 分区循环用
for j := low; j ,不是 <code>j ,否则 pivot 自身被重复比较
递归终止条件只能是 low >= high,不是 len(arr)
写成 if len(arr) 看似合理,实则错在语义混淆:<code>arr 是子切片,但 len(arr) 返回的是当前切片长度,而你的分区逻辑完全基于 low 和 high 坐标。一旦递归进入 arr[5:6](长度为 1),low=5, high=5,此时 low >= high 成立,应终止;但若用 len(arr) ,虽结果相同,却掩盖了坐标系统一致性这个关键约束。
<ul>
<li>所有递归分支都必须用同一套坐标系统,<code>low 和 high 是唯一可信的边界
high 是右闭区间端点,不是长度,所以 quickSort(arr, p+1, high) 合法,而 quickSort(arr[p+1:], ...) 会丢失坐标信息low > high)必须处理,否则某些 pivot 选择策略下可能无限递归别用 append 拼接左右子数组,原地交换才是 Go 的正确姿势
常见错误是这么写:left := append([]int{}, arr[low:p]...),再 append(append(left, pivot), right...)。这根本没改原切片——append 在底层数组容量不足时分配新内存,返回新切片头,原变量仍指向旧地址。你看到的“排序结果”只是临时副本,后续用 sort.Search 查不到任何东西。
- 所有元素移动必须通过
arr[i], arr[j] = arr[j], arr[i]完成,确保修改落在同一底层数组上 - 小数组优化(如
high-low )直接调用 <code>insertionSort(arr[low:high+1]),传入子切片但不新建 - 随机 pivot 要用
rand.Intn(high-low+1) + low,然后立刻 swap 到high位,避免后续逻辑假设 pivot 固定在末尾
手写快排前先问自己:真需要绕过 sort.Slice 吗?
sort.Slice 不仅防退化、支持泛型、自动处理 nil 和边界,还经过多年生产验证。你手写的版本哪怕只漏一个 if low > high { return },就可能在特定输入下 panic 或死循环。只有三类场景值得手写:算法题强制要求、教学演示分区过程、或需定制不可见行为(比如按绝对值排序且保持原序稳定性)。
-
sort.Slice的三个硬约束必须牢记:data不能为 nil、less函数必须满足严格弱序、字段访问前要判空(如a[i].Name != nil) - 如果只是为了“更快”,大概率失望——
sort.Slice对[]int有专门优化路径,手写版除非极致调优,否则常更慢 - 真正容易被忽略的,是分区函数里 pivot 归位后,必须返回它最终落定的索引;错返
i而非i+1,会导致左半段少排一个元素

















