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

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

归并排序基于分治思想,通过递归或迭代将数组拆分后合并。本文详细解析了 C 语言下的递归与非递归实现细节,重点讲解了区间划分逻辑、边界处理及稳定性分析。该算法时间复杂度稳定在 O(n log n),空间复杂度为 O(n),适用于对稳定性有要求或海量数据的外部排序场景。

AiEngineer发布于 2026/3/15更新于 2026/7/2431 浏览
归并排序详解:分治策略与 C 语言实现

归并排序:分治思想的经典实践

归并排序(Merge Sort)是分治法(Divide and Conquer)的典型应用,由约翰·冯·诺依曼于 1945 年提出。它的核心逻辑很简单:把大问题拆成小问题,解决完小问题再合并结果。

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

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

归并排序的关键操作在于合并两个有序数组。每次比较两个数组的首元素,将较小者放入新数组,直到所有元素合并完成。

归并排序过程演示

归并排序示意图

递归实现细节

在写递归代码时,区间划分是个容易踩坑的地方。我们来看具体的 C 语言实现。

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

// 辅助函数:合并两个有序区间 [begin, mid] 和 [mid+1, end]
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] 会导致死循环或逻辑错误
    _MergeSort(a, tmp, begin, mid);
    _MergeSort(a, tmp, mid + 1, end);

    // 归并过程
     begin1 = begin, end1 = mid;
     begin2 = mid + , end2 = end;
     i = begin;

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

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

    
    (a + begin, tmp + begin, (end - begin + ) * ());
}


  {
    
    * tmp = (*)(() * n);
     (tmp == ) {
        perror();
        ;
    }

    
    _MergeSort(a, tmp, , n - );

    
    (tmp);
    tmp = ;
}


  {
     a[] = {, , , , , };
    MergeSort(a, );
     ( i = ; i < (a) / (a[]); i++) {
        (, a[i]);
    }
     ;
}
int
int
1
int
while
if
else
// 处理剩余元素
while
while
// 将合并结果复制回原数组
memcpy
1
sizeof
int
// 归并排序入口函数
void
MergeSort
(int* a, int n)
// 分配临时数组空间
int
int
malloc
sizeof
int
if
NULL
"malloc fail"
return
// 调用递归函数
0
1
// 释放临时数组
free
NULL
// 测试案例
int
main
()
int
2
3
5
4
7
1
6
for
int
0
sizeof
sizeof
0
printf
"%d "
return
0

为什么区间不能是 [begin, mid-1] 和 [mid, end]?

假设我们有 10 个数据,下标从 0 到 9。如果采用 [begin, mid-1] 和 [mid, end] 的划分方式:

此时 mid = 4,分成 [0, 3] 和 [4, 9]。看似没问题,但在递归深入后会出现问题。

在 [0, 3] 区间里,mid = 1,mid-1 = 0。于是分成 [0, 0] 和 [1, 3]。 [0, 0] 满足终止条件跳出。 但在 [1, 3] 区间里,mid = 2,mid-1 = 1。于是分成 [1, 1] 和 [2, 3]。 到了 [2, 3] 区间,mid = 2,mid-1 = 1。这时左区间变成 [2, 1],右区间是 [2, 3]。 左区间起始大于结束,逻辑上显然不对,这会导致递归无法正确收敛。

所以标准的写法必须是 [begin, mid] 和 [mid+1, end]。

操作时间复杂度说明
分解数组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;
    }

    // gap 是每次归并数据的长度
    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;
        }

        // 第二组的 begin2 没越界,end2 越界了,需要修正边界
        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;
}

如果数组长度为 10,就会出现边界溢出的情况。通过打印归并区间分析,主要有两种边界情况:

  1. begin2 溢出:说明没有第二个子数组了,直接结束。
  2. begin2 不溢出,但 end2 溢出:说明第二个子数组不完整,需要修正 end2 为 n-1。

非递归实现要点:

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

时间复杂度同样是 O(n log n),因为每一层都是 O(n),一共 log n 层。空间复杂度也是 O(n)。

归并排序特性总结

  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. 结语
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 前端流式输出实现:原理与实践
  • Spring AI 自定义 Tool 调用返回值:实现 TodoList 提醒注入
  • 前端水印与反爬的实现思路
  • Gemini Pro 实测:多模态与代码能力的实际应用场景分析
  • 服务端高并发分布式架构演进之路
  • H.265 网页播放:WebAssembly + FFmpeg 硬软解兼容方案
  • 从卡顿到流畅:Tesla K80 显卡上的 llama.cpp CUDA 优化实战指南
  • 飞算 JavaAI 编程助手在 IDEA 中的安装教程:本地、离线及在线方法
  • Flutter 完整开发实战详解:从基础环境搭建到核心原理深入
  • Arduino 基于 6.5 寸轮毂电机的自动跟随机器人底盘超声波方案
  • 无代码方案:CRNN WebUI使用全指南
  • GitHub Copilot 学生身份认证流程与材料准备指南
  • IT 行业前景分析与零基础转行自我评估指南
  • 汽车雷达多径环境下的幽灵目标检测技术
  • 法奥机器人基础操作与编程指南
  • Kali Linux 2025.4 在 VMware 中鼠标无法显示问题解决方案
  • R 语言在 AIGC 时代的数据科学应用与实战
  • MySQL 数据库基础:概念、架构与核心使用指南
  • RxJava 源码深度解析:订阅流程与线程切换原理
  • AI 驱动的接口测试全流程自动化实现方法

相关免费在线工具

  • 加密/解密文本

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