最常见死循环是分区后子区间未真正缩小:若p==low时调用quickSort(arr,low,p-1)导致low>p-1,或p==high时调用quickSort(arr,p+1,high)传入无效区间;应首行加if low>=high{return},并确保左右子区间为[low,p-1]和[p+1,high]且p∈[low,high]。

partition 函数里 low 和 high 没收缩导致无限递归
最常见死循环不是逻辑错,而是分区后子区间没真正变小。比如 partition 返回了 p == low,而你接着调用 quickSort(arr, low, p-1),此时 low > p-1,但若递归入口没检查边界,下一轮仍会进同一段逻辑;更糟的是,若 p == high,quickSort(arr, p+1, high) 也会传入无效区间(如 5, 4),而你的终止条件只写 if high-low 或 if low 却漏判 low >= high,就会卡住。
实操建议:
- 所有递归入口第一行加
if low >= high { return },别依赖后续条件兜底 -
partition返回 pivot 索引后,确保左右子区间严格缩小:左段是[low, p-1],右段是[p+1, high],且必须验证p确实在[low, high]内(尤其随机 swap 后) - 在
partition循环里加计数器或日志,比如for j := low; j 中打印j和i,看是否陷入某次不增不减的循环
三数取中或随机 pivot 后没真正更新切片导致 pivot 偏移
比如你写了 randIndex := low + rand.Intn(high-low+1),然后 arr[randIndex], arr[high] = arr[high], arr[randIndex],看似把 pivot 换到末尾 —— 但如果 randIndex == high,这次 swap 实际什么都没做,而后续仍按 arr[high] 当 pivot,等于没换。更隐蔽的是:若你在 partition 外做了三数取中并 swap 到 low,但内部仍用 arr[high] 当 pivot,那 pivot 根本不是你选的那个。
实操建议:
- 随机 pivot 后,立刻用
pivot := arr[high](或统一用arr[low]),别复用旧变量 - 三数取中函数
GetMidIndex返回下标后,必须显式 swap 到你约定的 pivot 位置(比如末尾),再读值 - 调试时直接打印
arr[low], arr[(low+high)/2], arr[high]和最终pivot,确认三者关系符合中位数预期
小数组优化阈值设太高或没生效,触发退化路径
当输入是已排序数组(如 []int{1,2,3,4,5}),且你没做 pivot 优化,每次选最右元素,partition 总返回 high,导致左子区间长度恒为 n-1,递归深度达 O(n)。若你还把插入排序阈值设成 20,而数组长 1000,它永远进不了插入分支,一直递归直到栈溢出或死循环(取决于终止条件是否健壮)。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
实操建议:
- 小数组阈值建议 ≤12(和 Go 标准库对齐),别盲目设大;测试时专门喂升序/逆序/全同数组
- 在递归前加一行日志:
fmt.Printf("quickSort [%d:%d], len=%d\n", low, high, high-low+1),观察是否出现重复区间或长度不减 - 强制触发 fallback:临时把
if high-low 改成,看是否还卡住 —— 如果不卡了,说明原阈值让退化路径逃过了优化
sort.Slice 的闭包捕获变量引发隐式状态污染
这不是手写快排的 bug,但很多人混用时踩坑:比如你先用 sort.Slice 排一次,闭包里用了外部变量 order 控制升/降序,之后又在另一处改了 order,但没重定义闭包,导致第二次排序行为异常,看起来像“排序中途卡住”或结果错乱。
实操建议:
- 避免在
sort.Slice的比较函数里引用可变外部变量;需要参数化时,用立即执行函数传参,例如:sort.Slice(data, func(i,j int) bool { return compare(data[i], data[j], order) }) - 如果真要复用逻辑,把比较函数单独定义为纯函数,不闭包任何状态
- 死循环排查时,先注释掉所有
sort.Slice调用,确认问题是否还在 —— 很多时候你以为是快排 bug,其实是别的排序干扰了数据状态
关键点在于:快排死循环几乎总是源于分区逻辑和递归边界之间的错配,而不是算法思想本身。最容易被忽略的是——你写的 partition 返回值是否真的把数组分成了三个非重叠段,以及每一段的索引是否都比上一层严格缩小。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










