跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C算法

归并排序非递归实现详解

归并排序的非递归实现采用自底向上的策略,通过步长 gap 控制合并规模,从长度为 1 的子数组开始两两合并,逐步翻倍直至覆盖整个数组。相比递归版本,非递归需手动处理边界条件,特别是当数组长度不是 2 的幂次时,右区间可能越界或落单。代码中通过判断 begin2 和 end2 是否超出数组范围来修正逻辑,确保排序正确且安全。

编程诗人发布于 2026/3/27更新于 2026/6/1521 浏览
归并排序非递归实现详解

前言

非递归实现的归并排序通常被称为'自底向上'(Bottom-Up)版本。与递归版本不同,它不依赖栈回溯拆分,而是直接从长度为 1 的子数组开始,两两合并,步长翻倍(2, 4, 8...),直到覆盖整个数组。

核心思路

将递归逻辑改写为循环结构主要有两种常见方法:使用栈模拟或改为迭代。但在归并排序中,单纯用栈模拟往往只能处理拆分,难以在弹出时找回'合并'的上下文。因此,直接采用循环控制步长是更优解。

核心原理:

  1. 步长设为 1:将数组视为 N 个长度为 1 的有序子数组。
  2. 两两合并:相邻的两个长度为 1 的子数组合并成长度为 2 的有序子数组。
  3. 步长翻倍:将步长设为 2,继续合并成长度为 4 的有序子数组。
  4. 重复:直到步长超过数组长度,排序完成。

想象面前有一排乱序的扑克牌,先两两整理成有序对,再把这些对整理成有序的四张一组,以此类推。

流程演示

假设数组为 [10, 6, 7, 1, 3, 9, 4, 2],我们逐步观察循环如何控制归并过程。

文章配图

变量定义

  • gap: 当前子序列的长度
  • begin1, end1: 左半区间起点和终点
  • begin2, end2: 右半区间起点和终点

第一轮:gap = 1

此时每两个元素为一组进行归并。

  • 第一次合并:[10] 和 [6]。6 小于 10,交换位置。结果:[6, 10, 7, 1, 3, 9, 4, 2]
  • 第二次合并:[7] 和 [1]。1 小于 7,交换位置。结果:[6, 10, 1, 7, 3, 9, 4, 2]
  • 第三次合并:[3] 和 [9]。已有序,无需变动。
  • 第四次合并:[4] 和 [2]。2 小于 4,交换位置。

第一轮结束,数组变为两两有序:[6, 10, 1, 7, 3, 9, 2, 4]。

第二轮:gap = 2

此时每四个元素为一组,前两个和后两个归并。

  • 第一次归并:左 [6, 10],右 [1, 7]。比较后得到 [1, 6, 7, 10]。
  • 第二次归并:左 [3, 9],右 [2, 4]。比较后得到 [2, 3, 4, 9]。

第二轮结束,数组变为四四有序:[1, 6, 7, 10, 2, 3, 4, 9]。

第三轮:gap = 4

最后将前后两部分合并。

  • 归并:左 [1, 6, 7, 10],右 [2, 3, 4, 9]。最终得到完全有序数组 [1, 2, 3, 4, 6, 7, 9, 10]。

边界处理疑难点

1. 均分情况

当数组长度恰好是 2 的幂次(如 8),每一步都能完美凑齐左右半边,逻辑简单。

2. 不均分情况

现实中的数组长度往往不是 2 的整数次幂(如 10)。此时会出现'落单'或'越界'问题。

例如 gap=2 时,10 个元素分为 2+2+2+2+2,最后一个 2 没有配对对象;gap=4 时,分为 4+4+2,最后的 2 也是落单。

我们需要关注四个边界:begin1, end1, begin2, end2。其中只有 begin1 天然安全,其余三个都可能越界。

解决方案:

  1. 左数组落单:若 begin2 >= n,说明右半区间不存在,直接跳过本次循环。
  2. 右数组越界:若 end2 >= n,说明右半区间存在但长度不足,需将 end2 修正为 n - 1。

文章配图

3. 递归与非递归的区别

你可能会疑惑:'为什么递归版本不需要这么多边界判断?'

  • 递归(自顶向下):拥有自动适应的灵活性。它只关心'当前区间切两半',直到长度为 1 自然终止。边界会随递归深度自然收敛。
  • 非递归(自底向上):带有按规则执行的僵硬性。它严格遵循 1→2→4→8 的翻倍逻辑,必须手动处理因'框太大'导致的边界溢出问题。

代码实现

void MergeSortNonR(int* a, int n) {
    // 申请临时数组用于归并
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL) {
        perror("malloc fail\n");
        return;
    }

    // 自底向上,初始步长为 1
    int gap = 1;
    while (gap < n) {
        for (int i = 0; i < n; i += 2 * gap) {
            // 定义左右子数组的边界
            int begin1 = i, end1 = i + gap - 1;
            int begin2 = i + gap, end2 = i + gap * 2 - 1;

            // 如果右子数组起点已越界,说明左子数组落单,无需归并
            if (begin2 >= n) break;

            // 如果右子数组终点越界,修正为数组末尾
            if (end2 >= n) end2 = n - 1;

            // 归并到临时数组
            int j = begin1;
            while (begin1 <= end1 && begin2 <= end2) {
                if (a[begin1] <= a[begin2]) {
                    tmp[j++] = a[begin1];
                    begin1++;
                } else {
                    tmp[j++] = a[begin2];
                    begin2++;
                }
            }
            // 拷贝剩余元素
            while (begin1 <= end1) {
                tmp[j++] = a[begin1];
                begin1++;
            }
            while (begin2 <= end2) {
                tmp[j++] = a[begin2];
                begin2++;
            }
            // 将归并结果拷回原数组
            memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
        }
        gap *= 2;
    }
    free(tmp);
}

这段代码通过 begin2 和 end2 的判断,优雅地解决了任意长度数组的归并问题。实际运行时,注意内存分配失败的处理以及 memcpy 的范围计算,确保不会发生缓冲区溢出。

目录

  1. 前言
  2. 核心思路
  3. 流程演示
  4. 变量定义
  5. 第一轮:gap = 1
  6. 第二轮:gap = 2
  7. 第三轮:gap = 4
  8. 边界处理疑难点
  9. 1. 均分情况
  10. 2. 不均分情况
  11. 3. 递归与非递归的区别
  12. 代码实现
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 前端监控实战:构建可观测的 Web 应用
  • Python 实战:2025 中秋月相计算与月球数据可视化
  • 网络安全行业发展前景与零基础转行建议
  • AI 鉴伪技术深度解析:应对 Deepfake 与文档篡改的安全方案
  • SciChart.js v5 发布:Web 图表性能与功能升级
  • 前端转产品经理一年经验总结与思考
  • Kotlin 面试算法实战:二叉树遍历与转换
  • 算法实战:寻找数组中心下标与除自身外数组乘积
  • Qwen3-ASR-1.7B 赋能博物馆 AR 导览:语音转写与知识图谱联动
  • Ubuntu 系统下 Python 连接 KingbaseES 数据库实现增删改查
  • Qwen3-VL-WEBUI 架构与 Instruct/Thinking 双模式实战
  • 人工智能进化全景:从专用工具到超级智能的跃迁
  • C++ 数据结构基础:树、二叉树及堆的特性与实现
  • 基于 Python 与开源 AI 构建本地智能问答系统
  • C++ 核心特性详解:函数重载、引用、内联函数、auto 与 nullptr
  • 位图矢量化技术瓶颈突破:Potrace 算法深度解析与应用实践
  • Spring Boot 集成 MyBatis 操作数据库详解
  • Python 3.12.0 Windows 安装与环境配置指南
  • 无人机 5.8G 模拟图传电路设计方案及性能分析
  • Whisper.cpp 量化版本清单与 ggml 格式模型下载

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online