mergeSort在Go中仅适用于需稳定排序或自定义合并的场景;业务代码应优先用sort.Ints或sort.Slice;递归需传left/right索引,mid=left+(right-left)/2防溢出;终止条件为left>=right。

mergeSort 在 Go 里不是“拿来就能用”的通用排序工具,它只在特定场景下值得手写:你需要稳定排序 + 自定义合并逻辑(比如按时间戳分段归并日志),或正在刷算法题、做教学演示。业务代码中直接用 sort.Ints 或 sort.Slice 更安全、更快、更少出错。
递归入口的 mid 计算必须基于当前区间
错误写法:mid := len(nums) / 2 —— nums 始终是原始切片,len(nums) 永远不变,导致无限递归。
正确做法:传入 left 和 right 索引,用 mid := left + (right-left)/2 计算中点。
- 防整数溢出,比
(left+right)/2更健壮 - 确保每次拆分落在
[left, right]范围内,不会漏元素或越界 - 终止条件必须是
left >= right,不是len(nums)
merge 函数必须复用同一块临时空间
常见错误是每层递归都 make([]int, len(left)+len(right)),既浪费内存又加剧栈压力。
立即学习“go语言免费学习笔记(深入)”;
正确模式:顶层预分配一次 temp := make([]int, len(nums)),然后所有 merge 调用共用它。
-
merge函数签名应为func merge(nums []int, temp []int, left, mid, right int) - 合并时只写入
temp[0:k],再用copy(nums[left:right+1], temp[:k])写回原数组 - 禁止用
append构造新切片,会丢失原底层数组引用,破坏原地性
小数组必须设阈值切回插入排序
当 right - left < 16(经验值)时,继续递归不仅没性能收益,反而增加栈帧和切片头开销。
Go 默认 goroutine 栈仅 2KB,n=10⁶ 时理论递归深度约 20 层,但加上调试信息、切片头、临时变量,很容易突破上限。
- 在
mergeSort入口加判断:if right - left < 16 { insertionSort(nums[left:right+1]); return } - 插入排序对小数组常数更小,且无递归开销
- 这个阈值不固定,可按实际数据分布微调;低于 8~16 通常就该退避
什么时候该直接放弃手写?
如果你只是想把一个 []int 排好序,或者按结构体某个字段排序,sort.Ints(nums) 和 sort.Slice(students, func(i, j int) bool { return students[i].Score < students[j].Score }) 就是最终答案。
- 标准库内部已做混合策略:小数组插排、中等规模归并、大数组堆排,规避快排最坏 O(n²)
- 它经过大量真实场景打磨,GC 友好,边界处理严密,且支持
sort.Stable - 手写版唯一不可替代的点,是你要控制合并过程本身(比如带 offset 的流式归并、多路外部归并)
真正容易被忽略的,是「临时空间复用」和「小数组阈值」这两个点——它们不改变算法复杂度,却直接决定你的归并排序在 n=10⁵ 以上会不会 panic。而多数人卡在第一关:连 mid 都算错了,还谈什么优化。


















