【数据结构】八大排序之归并排序:分治思想的完美演绎

【数据结构】八大排序之归并排序:分治思想的完美演绎

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

基本思想

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

算法流程分为两个核心阶段:

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

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

在这里插入图片描述
在这里插入图片描述
#include<string.h>#include<stdio.h>#include<stdlib.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));//begin和end在变化不是一直从0开始 看动图}// 归并排序入口函数voidMergeSort(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;}//测试案例intmain(){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]);}return0;}

解释一下为什么区间不能用[end.mid-1]和[mid,end]

在这里插入图片描述

假设10个数据 下标从0到9 如果是[begin,mid-1] 和[mid,end]

此时mid-1=3

所以就分成[0,3]和[4,9]两个区间

在[0,3]这个区间里 mid-1=0

所以分成[0,0]和[1,3]

[0,0]此时就跳出循环

[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),但通常临时数组占主导

非递归归并

在这里插入图片描述

11归并可以理解为两组数据中的个数分别为1

11归并完进行22归并 以此类推

voidMergeSortNonR(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)//一整组归并是gap+gap个 i代表每组归并的起始位置{int begin1 = i, end1 = i+gap-1;int begin2 = gap +1, end2 = i+2*gap-1;//begin2是begin1跳过一组数据,也就是跳过gap个 所以是gap+i;end2要跳过两组.//第二组越界不存在.这一组就不需要归并了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个数就会出现溢出

把归并区间打印出来分析一下 就上图者三种情况

但也可以分为两种 第一种是begin2溢出 第二种是begin2不溢出,end2溢出

时间复杂度

不能看循环层数

每一层都是n 一共logn层

所以时间复杂度是nlongn

空间复杂度是O(n) 因为要再开一块空间

非递归实现要点

  1. gap参数:控制当前归并的子数组大小,从1开始,每次迭代翻倍
  2. 边界处理:关键步骤,处理数组大小不是2的幂的情况:
    • 当第二个子数组完全越界(begin2 ≥ n),跳过本次合并
    • 当第二个子数组部分越界(end2 ≥ n),修正其边界为n-1
  3. 迭代过程
    • 第1轮:11归并 → 每个子数组大小为1
    • 第2轮:22归并 → 子数组大小为2
    • 第k轮:2ᵏ归并 → 子数组大小为2ᵏ

归并排序特性总结

  1. 稳定排序
    • 当两个元素相等时,归并排序优先选择左子数组的元素
    • 保持相等元素的原始相对位置不变
    • 适用于需要稳定性的场景(如多关键字排序)
  2. 时间复杂度
    • 最优:O(n log n)
    • 平均:O(n log n)
    • 最差:O(n log n)
    • 不受输入数据影响,性能稳定
  3. 空间复杂度
    • O(n) 额外空间
    • 递归实现还有 O(log n) 的栈空间
  4. 外排序优势
    • 归并排序是少数能高效处理外部存储(如硬盘)数据的排序算法
    • 特别适合处理超过内存容量的海量数据
    • 工作流程:
      1. 将大文件分割为能放入内存的小块
      2. 在内存中排序每个小块
      3. 合并已排序的小块

结语

归并排序是分治思想的完美体现,通过"分而治之"的策略达到O(n log n)的高效排序。其核心优势在于:

  1. 稳定可靠:不受输入数据影响,始终保证O(n log n)性能
  2. 外排序能力:唯一能高效处理海量磁盘数据的通用排序算法
  3. 稳定排序:保持相等元素的原始顺序
  4. 并行潜力:天然适合并行化实现

尽管需要O(n)额外空间,但在现代计算机系统中,空间换时间的策略往往是值得的。归并排序在数据库系统、大数据处理、外部排序等场景中发挥着不可替代的作用,是每个程序员必须掌握的经典算法之一。

Read more

【DeepSeek应用】100个 DeepSeek 官方推荐的工具箱

【DeepSeek应用】100个 DeepSeek 官方推荐的工具箱

【DeepSeek应用】Deepseek R1 本地部署(Ollama+Docker+OpenWebUI) 【DeepSeek应用】DeepSeek 搭建个人知识库(Ollama+CherryStudio) 【DeepSeek应用】100个 DeepSeek 官方推荐的工具箱 【DeepSeek应用】Zotero+Deepseek 阅读与分析文献 【DeepSeek应用】100个 DeepSeek 官方推荐的工具箱 * 1. DeepSeek 工具箱:应用程序 * 2. DeepSeek 工具箱:AI Agent 框架 * 3. DeepSeek 工具箱:RAG 框架 * 4. DeepSeek 工具箱:即时通讯软件 * 5. DeepSeek 工具箱:浏览器插件 * 6. DeepSeek 工具箱:

By Ne0inhk
“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

大模型仍未对上商业的齿轮? 编译 | 王启隆 来源 | youtu.be/aWqfH0aSGKI 出品丨AI 科技大本营(ID:rgznai100) 现在的硅谷,空气里都飘着一股“再不上车就晚了”的焦躁感。 最近 OpenClaw 风头正旺,强势登顶 GitHub,终结了 React 神话,许多人更是觉得“AI 自己干活赚钱”的日子就在明天了。 特别是在斯坦福商学院(GSB)这种地方,台下坐着的都是成天琢磨怎么用下一个技术风口搞个独角兽出来的狠人。 微软的首席科学官(CSO)Eric Horvitz 被请到了这个几乎全美最想用 AI 变现的礼堂里。作为从上世纪 80 年代就开始搞 AI 的绝对老炮、也是微软技术底座的“扫地僧”,这位老哥并没有顺着台下的胃口,去吹捧下个月大模型又要颠覆什么行业,而是兜头给大家浇了一盆带点学术味的冷水。 他讲了一个挺有画面感的比喻:大家都在聊

By Ne0inhk
Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 当大模型能在几秒钟内生成一段“看起来像那么回事”的补丁时,开源社区却开始付出另一种代价。 最近,开源游戏引擎 Godot 的核心维护团队公开吐槽:他们正被大量“AI 生成的低质量代码”淹没。那些代码往往结构完整、注释齐全、描述洋洋洒洒,但真正的问题是——提交者可能并不理解自己交上来的内容。 这件事,并不是简单的“有人偷懒用 AI 写代码”。它正在触及开源协作最核心的东西:信任。 一场悄无声息的“AI 洪水” 事情的导火索来自一条 Bluesky 讨论帖。 Godot 主要维护者之一、同时也是 Godot 商业支持公司 W4 Games 联合创始人的 Rémi Verschelde 表示,所谓的“AI slop”

By Ne0inhk
诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

当宇宙级的“嘴炮”遇到降维打击。 编译 | 王启隆 来源 | youtu.be/l6ZcFa8pybE 出品丨AI 科技大本营(ID:rgznai100) 打开最新一期知名播客 StarTalk 的 YouTube 评论区,最高赞的一条留言是这样写的: “我长这么大,第一次看到尼尔·德葛司·泰森(Neil deGrasse Tyson)在一档节目里几乎全程闭嘴,像个手足无措的小学生一样乖乖听讲。” 作为全美最知名的天体物理学家,泰森平时的画风是充满激情、喋喋不休、用宇宙的宏大来震撼嘉宾。但这一次,坐在他对面的那位满头银发、带着温和英音的英国老人,仅仅用最平淡的语气,就让整个演播室陷入了数次令人窒息的沉默。 这位老人是 Geoffrey Hinton。深度学习三巨头之一,2024 年诺贝尔物理学奖得主,被公认为“AI 教父”。 对经常阅读 Hinton 演讲的我来说,这也是比较新奇的一幕—

By Ne0inhk