QuickSort 递归实现易栈溢出,Go 中常见切片分段递归+append拼接的写法效率低且不安全,应改用原地分区与尾递归优化或迭代实现。

QuickSort 的递归实现容易栈溢出,别直接照抄示例
Go 里写 QuickSort,网上很多示例用切片分段递归再 append 拼接,比如:
func QuickSort(arr []int) []int {
if len(arr) <= 1 {
return arr
}
pivot := arr[0]
var left, right []int
for _, v := range arr[1:] {
if v < pivot {
left = append(left, v)
} else {
right = append(right, v)
}
}
return append(append(QuickSort(left), pivot), QuickSort(right)...)
}
这段代码逻辑清晰,但问题很实际:
- 每次递归都新建两个切片,内存分配频繁,GC 压力大
- append 拼接会触发底层数组复制,尤其对大数组性能跳变明显
- 最坏情况(已排序数组选首元素为 pivot)递归深度达 O(n),可能爆栈
更稳妥的做法是原地分区 + 尾递归优化:
- 用
arr[left:right]视图传参,不复制数据 - pivot 选中位数或随机索引,避免最坏退化
- 递归调用只保留一个分支(如先排左半区,右半区用 for 循环迭代处理)
BinarySearch 要求输入必须有序,但没人帮你校验
立即学习“go语言免费学习笔记(深入)”;
几乎所有 Go 版 BinarySearch 实现都假设输入已升序排列,例如:
func BinarySearch(arr []int, target int) int {
left, right := 0, len(arr)-1
for left <= right {
mid := left + (right-left)/2
if arr[mid] == target {
return mid
} else if arr[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return -1
}
但它不会检查 arr 是否真有序。一旦传入乱序切片,返回结果完全不可信——既不是找不到,也不是位置错,而是“看起来找到了,其实没找对”。
真实项目中建议:
- 开发期加断言:用
sort.IsSorted(sort.IntSlice(arr))快速校验(仅 debug 构建启用) - 生产环境若无法保证有序,直接改用
sort.SearchInts(标准库函数,内部带有序性假设,但文档明确警告行为未定义) - 更稳妥的方案是先
sort.Ints(arr)再查,但得权衡排序开销 vs 查找频次
fibonacci 迭代版比递归版快百倍,但要注意整数溢出边界
递归写法 fibonacci(n int) int { if n < 2 { return n }; return fibonacci(n-1) + fibonacci(n-2) } 看似简洁,时间复杂度是 O(2^n),算 f(40) 就明显卡顿。
迭代版没问题:
func fibonacci(n int) int {
if n <= 1 {
return n
}
a, b := 0, 1
for i := 2; i <= n; i++ {
a, b = b, a+b
}
return b
}
但注意:int 在 64 位系统是 int64,f(93) 就溢出(2^63-1 ≈ 9.2e18),而 f(94)=19740274219868223167 已超限。
如果业务需要更大值,得换 math/big.Int,但代价是堆分配和运算变慢;若只是做练习或索引,加个 if n > 92 { panic("fibonacci: n too large") } 更实际。
递归算法的边界条件和切片视图操作最容易漏掉空 nil 判断
所有涉及 []int 参数的递归函数,比如 MergeSort、QuickSort、甚至 binarySearch,都默认接收非 nil 切片。但 Go 中 var x []int 是 nil,len(x) 返回 0,x[0] panic,而 len(x) <= 1 判断会把 nil 当成空切片通过。
后果是:传入 nil 时,有些实现返回空切片看似正常,但 merge 函数里 left[0] 或 right[0] 会直接 panic。
务必在入口加:
-
if arr == nil { return nil }(保持语义一致) - 或统一用
if len(arr) == 0 { return arr },因为len(nil) == 0,且多数标准库函数也按此约定
这点在单元测试里常被忽略,一上生产遇到空请求参数就崩。


















