前言
非递归实现的归并排序,通常被称为'自底向上'(Bottom-Up)版本。与递归版本不同——后者先将数组对半拆分直到只剩一个元素,再通过递归栈回溯合并——非递归版本直接从最小的子数组(长度为 1)开始,两两合并,然后步长翻倍(2, 4, 8...),直到整个数组有序。
核心思路:为什么不用栈模拟?
将递归转换为非递归主要有两种路径:用栈结构模拟递归过程,或者直接将逻辑改写为循环。但在归并排序中,单纯用栈模拟往往行不通。
假设我们要排序数组 arr = [8, 4, 5, 7],下标是 0 到 3。
初始状态:栈中有任务 [0, 3]。 第一步:弹出 [0, 3]。计算 mid = 1。拆分为左 [0, 1] 和右 [2, 3]。将这两个区间压入栈。 关键点: [0, 3] 被彻底扔了,没人记得原本是要合并这俩段。 后续步骤同理,[0, 1] 也会被拆解成单元素后丢弃。
问题出现了: 当 [0, 0] 和 [1, 1] 都处理完了,按理说应该执行 merge(0, 0, 1) 把它们变成 [4, 8]。但是!栈里没有这个指令,之前的父区间早就被丢弃了,程序不知道这两个单元素数组是'兄弟'关系。
因此,我们采用循环结构。递归是'先拆分,后合并',而非递归则是直接从最小单元开始'只合并'。
算法流程
- 步长(gap)设为 1:将数组看作由 N 个长度为 1 的有序子数组组成。
- 两两合并:相邻的两个长度为 1 的子数组合并成长度为 2 的有序子数组。
- 步长翻倍:将步长设为 2,相邻的两个长度为 2 的子数组合并成长度为 4。
- 重复:步长继续翻倍(4, 8, 16...),直到步长超过数组长度。
简单理解:想象面前有一排乱序的扑克牌。先把相邻的每 1 张牌看作一组,两两合并成有序的 2 张牌;再把相邻的每 2 张牌看作一组,两两合并成有序的 4 张牌……直到步长超过数组长度,排序完成。
实现细节演示
假设存在如下数组 [10, 6, 7, 1, 3, 9, 4, 2],我们来看看循环结构是如何控制归并过程的。

核心变量
- gap: 当前子序列的长度
- 左数组区间:
[begin1, end1] - 右数组区间:
[begin2, end2]
第一轮:gap=1
此时我们将数组视为 8 个长度为 1 的子数组。
- 第一次合并:左
[0,0] => {10},右[1,1] => {6}。6 小于 10,交换位置。结果:[6, 10, 7, 1, 3, 9, 4, 2] - 第二次合并:左
[2,2] => {7},右[3,3] => {1}。1 小于 7,交换位置。结果:[6, 10, 1, 7, 3, 9, 4, 2] - 第三次合并:左
[4,4] => {3},右[5,5] => {9}。3 小于 9,无需变动。结果不变。 - 第四次合并:左
[6,6] => {4},右[7,7] => {2}。2 小于 4,交换位置。结果:




