归并排序
面试官问:"快速排序是稳定的吗?"
候选人小张回答:"是稳定的。"
面试官又问:"那归并排序呢?它和快速排序的区别是什么?"
小张开始支支吾吾...
一、从一个问题开始
归并排序是面试中另一个高频考点。和快速排序一样,它也是基于分治思想,但两者的实现和特性有很大不同。
90%的候选人知道归并排序是稳定的,但能讲清楚"什么是稳定"、为什么归并排序稳定、什么时候用归并排序的,不超过30%。
今天,我们把归并排序讲透。
【直观类比】
归并排序就像合并两个有序的扑克牌堆:
- 把牌分成两堆(分治)
- 把两堆分别排好序
- 把两堆按顺序合并成一大堆
关键在于:分牌的时候,牌的相对位置不变。这就是"稳定"的含义。
二、核心原理:分而治之
2.1 归并排序的步骤
2.2 合并两个有序数组
归并排序的核心是合并两个有序数组:
2.3 完整的归并排序
三、快速排序 vs 归并排序
3.1 对比表格
3.2 为什么归并排序是稳定的?
关键在合并过程:
因为相等时选择左边的元素,所以相同元素的相对顺序保持不变。
3.3 为什么快速排序是不稳定的?
快速排序的分区过程可能改变相同元素的相对位置:
四、迭代版归并排序(自底向上)
递归版归并排序需要O(logn)的栈空间,迭代版可以避免这个问题:
五、外部排序与海量数据
归并排序真正发挥威力的是外部排序(处理无法一次性加载到内存的数据)。
5.1 外部排序的思路
5.2 归并路数与磁盘I/O
归并路数(degree)越多,合并次数越少,但每次合并需要的内存越多。
六、面试高频追问
6.1 追问一:为什么归并排序需要O(n)额外空间?
因为合并两个有序数组时,需要一个额外的数组来存放合并结果。这个额外空间与原数组大小成正比。
6.2 追问二:能否优化到O(1)空间?
可以,但代价是时间复杂度变高:
- 方案A:原地归并,但需要额外移动操作
- 方案B:用链表可以直接原地归并
6.3 追问三:Java的Arrays.sort用什么排序?
七、边界与特例
7.1 空数组和单元素数组
7.2 完全有序数组
归并排序不管数组是否有序,时间复杂度都是O(nlogn)。
7.3 递归深度
对于n=10亿的数组,递归深度是log₂(10亿) ≈ 30,不会栈溢出。
八、常见误区
❌ 误区一:归并排序一定比快速排序慢
实际情况:在需要稳定性的场景下,归并排序是唯一选择;在随机数据下,两者性能相近。
❌ 误区二:归并排序的空间复杂度无法优化
实际情况:可以用链表实现原地归并,不需要额外数组。
❌ 误区三:外部排序只能归并排序
实际情况:外部排序适合用归并,因为可以流式处理数据。
九、记忆技巧
用一句话记住快排和归并的区别:
快排先分区后递归,归并先递归后合并;快排原地不合并,归并合并要空间
用口诀记住归并排序:
先对半切,分到最小,合并时从小到大
十、实战检验
检验一:力扣21题 - 合并两个有序链表
检验二:剑指offer - 数组中的逆序对
十一、总结
归并排序的核心是分治合并:
- 分:把数组分成两半
- 治:递归排序左右两半
- 合:合并两个有序数组
记住这三句话:
- 归并排序是稳定的,快速排序是不稳定的
- 归并排序需要O(n)额外空间,快速排序是O(1)
- 归并排序是外部排序的首选
下一篇文章,我们来聊聊堆排序,看看如何用二叉堆实现原地排序。