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

归并排序详解:分治策略与 C 语言实战

综述由AI生成归并排序基于分治思想,通过递归拆分和有序合并实现排序。核心在于合并两个有序子数组,时间复杂度稳定为 O(n log n),空间复杂度 O(n)。该算法具有稳定性,适合处理海量数据的外排序场景。结合 C 语言代码演示了递归与非递归两种实现方式,并分析了区间划分的关键细节及边界处理逻辑。

FlinkHero发布于 2026/3/29更新于 2026/6/922 浏览
归并排序详解:分治策略与 C 语言实战

归并排序:分治思想的完美演绎

基本思想

归并排序(Merge Sort)是分治法(Divide and Conquer)的经典应用,由计算机科学先驱约翰·冯·诺依曼于 1945 年提出。核心逻辑很简单:将大问题拆解为小问题,解决后再合并结果。

算法流程主要包含两个阶段:

  1. 分(Divide):递归地将当前数组分割成两个子数组,直到每个子数组只包含一个元素(此时天然有序)。
  2. 治(Conquer):将两个有序子数组合并成一个新的有序数组。

归并的核心操作是合并两个有序数组:每次比较两个数组的首元素,将较小者放入新数组,直到所有元素合并完成。

归并排序过程演示

归并排序示意图

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 递归辅助函数
void _MergeSort(int* a, int* tmp, int begin, int end) {
    // 递归终止条件:当子数组只有一个元素时
    if (begin == end) {
        return;
    }

    // 计算中间点
    int mid = (begin + end) / 2;
    
    // 注意区间划分:[begin, mid] 和 [mid+1, end]
    // 如果分为 [begin, mid-1] 和 [mid, end] 会有坑,比如数据 2 3 6 7
    _MergeSort(a, tmp, begin, mid);
    _MergeSort(a, tmp, mid + 1, end);

    // 归并两个有序区间
    int begin1 = begin, end1 = mid;
    int begin2 = mid + 1, end2 = end;
    int i = begin;

    while (begin1 <= end1 && begin2 <= end2) {
        if (a[begin1] < a[begin2]) {
            tmp[i++] = a[begin1++];
        } else {
            tmp[i++] = a[begin2++];
        }
    }

    // 处理剩余元素
    while (begin1 <= end1) {
        tmp[i++] = a[begin1++];
    }
    while (begin2 <= end2) {
        tmp[i++] = a[begin2++];
    }

    // 将合并结果复制回原数组
    memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}

// 归并排序入口函数
void MergeSort(int* a, int n) {
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL) {
        perror("malloc fail");
        return;
    }

    _MergeSort(a, tmp, 0, n - 1);
    free(tmp);
    tmp = NULL;
}

// 测试案例
int main() {
    int a[] = {2, 3, 5, 4, 7, 1};
    MergeSort(a, 6);
    for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
        printf("%d ", a[i]);
    }
    return 0;
}
为什么区间不能用 [begin, mid-1] 和 [mid, end]?

这是一个常见的实现陷阱。假设 10 个数据,下标从 0 到 9,mid 为 4。 如果是 [begin, mid-1] 即 [0, 3] 和 [mid, end] 即 [4, 9]。 在 [0, 3] 区间里,mid-1 会变成 0,分成 [0, 0] 和 [1, 3]。 而在 [1, 3] 中,mid-1 变成 1,分成 [1, 1] 和 [2, 3]。 到了 [2, 3],mid-1 依然是 1,导致分成 [2, 1] 和 [1, 3],出现死循环或逻辑错误。

操作时间复杂度说明
分解数组O(log n)递归深度
合并子数组O(n)每层需要遍历所有元素
总复杂度O(n log n)稳定的高效率排序
空间复杂度

归并排序需要 O(n) 的额外空间:

  • 用于存储合并过程中的临时数组
  • 递归调用栈空间 O(log n),通常临时数组占主导

非递归归并实现

非递归版本可以理解为自底向上的归并:先 1+1 归并,再 2+2 归并,以此类推。

非递归归并示意

void MergeSortNonR(int* a, int n) {
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL) {
        perror("malloc fail");
        return;
    }

    int gap = 1;
    for (int i = 0; i < n; i += 2 * gap) {
        int begin1 = i, end1 = i + gap - 1;
        int begin2 = i + gap, end2 = i + 2 * gap - 1;

        // 第二组完全越界,跳过本次合并
        if (begin2 >= n) {
            break;
        }

        // 第二组部分越界,修正边界
        if (end2 >= n) {
            end2 = n - 1;
        }

        int j = i;
        while (begin1 <= end1 && begin2 <= end2) {
            if (a[begin1] < a[begin2]) {
                tmp[j++] = a[begin1++];
            } else {
                tmp[j++] = a[begin2++];
            }
        }

        while (begin1 <= end1) {
            tmp[j++] = a[begin1++];
        }
        while (begin2 <= end2) {
            tmp[j++] = a[begin2++];
        }

        memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));
    }
    free(tmp);
    tmp = NULL;
}

非递归边界情况

如果数据量不是 2 的幂次方,就会出现溢出或越界。把归并区间打印出来分析,主要有两种情况:

  1. begin2 直接溢出,无需归并。
  2. begin2 未溢出,但 end2 溢出,需修正 end2 为 n-1。

非递归实现要点:

  • gap 参数:控制当前归并的子数组大小,从 1 开始,每次迭代翻倍。
  • 边界处理:关键步骤,处理数组大小不是 2 的幂的情况。
  • 迭代过程:第 1 轮 1+1 归并,第 2 轮 2+2 归并,第 k 轮 2^k 归并。

归并排序特性总结

  1. 稳定排序:

    • 当两个元素相等时,优先选择左子数组的元素。
    • 保持相等元素的原始相对位置不变。
    • 适用于多关键字排序等对稳定性有要求的场景。
  2. 时间复杂度:

    • 最优、平均、最差均为 O(n log n)。
    • 不受输入数据影响,性能非常稳定。
  3. 空间复杂度:

    • O(n) 额外空间。
    • 递归实现还有 O(log n) 的栈空间开销。
  4. 外排序优势:

    • 归并排序是少数能高效处理外部存储(如硬盘)数据的排序算法。
    • 适合处理超过内存容量的海量数据。
    • 工作流程:大文件分割 -> 内存排序小块 -> 合并已排序小块。

结语

归并排序是分治思想的典型体现,通过'分而治之'的策略达到 O(n log n) 的高效排序。尽管需要 O(n) 额外空间,但在现代计算机系统中,空间换时间的策略往往是值得的。其核心优势在于稳定可靠、性能不受数据分布影响,以及强大的外排序能力。在数据库系统、大数据处理等场景中,它发挥着不可替代的作用。

目录

  1. 归并排序:分治思想的完美演绎
  2. 基本思想
  3. 为什么区间不能用 [begin, mid-1] 和 [mid, end]?
  4. 空间复杂度
  5. 非递归归并实现
  6. 归并排序特性总结
  7. 结语
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • C++性能优化:提升代码执行效率的核心技巧
  • GitHub Copilot 实战:AI 辅助编程效率提升指南
  • Linux 环境下 OpenClaw 安装、初始化与 Web UI 配置指南
  • C++ 性能优化实战:从内存管理到 CPU 指令的效率提升
  • Linux 网络编程入门:Socket 编程详解
  • 堪称全网最详细的前端面试八股文,面试必备(附答案)
  • 网络安全零基础入门与学习路径指南
  • OpenClaw 对接飞书群机器人配置踩坑:消息不回与 Gateway 断开排查
  • C++ 实现 Wishart 分布样本矩阵生成
  • C++ 程序打包成 SO 库并调用的完整指南
  • LeetCode 124 二叉树中的最大路径和解题详解
  • 深入理解大模型预训练与微调:Pre-training 与 Fine-tuning 技术对比
  • 2026 年 AI 辅助编程工具全景对比:Copilot、Cursor、Claude Code 与 Codex 深度解析
  • 预训练语言模型与 BERT 实战应用
  • C++ 测试与调试实战:保障代码质量与稳定性
  • 人脸识别安全审计:基于 RetinaFace 与 CurricularFace 构建测试环境
  • LangChain.js 实战指南:基础调用与功能应用
  • MySQL 数据库基础:概念、架构与核心使用指南
  • Python 测试工程师使用 Faker 库生成测试数据
  • pytestx:专注任务调度的接口自动化测试平台

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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