排序算法——归并排序(递归与非递归)

归并排序

以升序为例

文章目录

  • 归并排序
    • 基本思想
    • 核心步骤
    • 递归写法
      • 实现代码
    • 非递归
      • 处理边界情况
      • 实现代码
    • 时间复杂度

基本思想

  • 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
  • 如果对两个有序序列的归并操作还不太熟悉,建议先看看合并两个有序链表

核心步骤

在这里插入图片描述

  • 由上图我们可以看到,归并排序首先要对待排序列不断二分,直到分成不可分割的子序列(即只有一个元素的序列,相当于有序)
  • 然后,再对有序的子序列不断进行归并操作,最后得到完全有序的序列。
  • 归并排序有递归和非递归两种写法,接下来我们来讨论如何实现的具体细节:

递归写法

  • 首先我们要注意,在进行归并操作时,为了防止原序列的元素被覆盖而导致排序错误,我们需要向内存申请一块空间用来临时存放合并的序列,同时,由于归并的此时不止一次,为防止多次申请内存而导致效率不高,我们直接向内存申请一块和原序列大小相等的空间
void MergeSort(int *nums, int numsSize)
{int* temp = (int*)malloc(sizeof(int) * numsSize);…………;free(temp);                           
}
  • 同时,归并排序在进行归并操作时需要知道每个子序列的区间,由于递归参数的限制,我们需要再定义一个子函数MergeSort(),并对这个子函数递归
void _MergeSort(int *nums, int *temp, int left, int right)
{…………;
}void MergeSort(int *nums, int numsSize)
{int* temp = (int*)malloc(sizeof(int) * numsSize);_MergeSort(nums, temp, 0, numsSize - 1);free(temp);                           
}
  • 易知,当子序列长度为1时,就可以不再进行二分
void _MergeSort(int *nums, int *temp, int left, int right)
{if (left >= right)return;…………;
}
  • 对待排序列的左半部分和右半部分不断递归分割
void _MergeSort(int *nums, int *temp, int left, int right)
{if (left >= right)return;int mid = (right - left) / 2 + left;_MergeSort(nums, temp, left, mid);_MergeSort(nums, temp, mid + 1, right);……………;
}
  • 接下来,就是对两个有序序列的合并操作
  • 注:可以走到合并这一步说明待合并的两个序列[left,mid]和[mid + 1,right]是有序的,存在两种情况:
    • 情况一:例如序列{9,2},进入函数_MergeSort()后,其子序列是单个数字,满足left >= right的条件,直接退出递归,开始合并。
    • 情况二:例如序列{9,2,5,4},进入函数_MergeSort()后,子序列{9,2}递归,合并后退出递归,然后子序列{5,4}递归,合并,退出递归,最后就变成了两个有序序列{2,9}和{4,5}的合并
    • 建议对递归不是很清楚的小伙伴可以尝试画画递归展开图,这对了解递归逻辑大有裨益。
void _MergeSort(int *nums, int *temp, int left, int right)
{if (left >= right)return;//递归分割int mid = (right - left) / 2 + left;_MergeSort(nums, temp, left, mid);_MergeSort(nums, temp, mid + 1, right);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = left;//归并while (begin1 <= end1 && begin2 <= end2){if (nums[begin1] > nums[begin2])temp[index++] = nums[begin2++];elsetemp[index++] = nums[begin1++];}while (begin1 <= end1)temp[index++] = nums[begin1++];while (begin2 <= end2)temp[index++] = nums[begin2++];//将temp暂时存储的数据覆盖待排序列nums原有位置的数据,实现待排序列区间有序for (int i = left; i <= right; i++)nums[i] = temp[i];
}

实现代码

void _MergeSort(int *nums, int *temp, int left, int right)
{if (left >= right)return;//递归分割int mid = (right - left) / 2 + left;_MergeSort(nums, temp, left, mid);_MergeSort(nums, temp, mid + 1, right);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = left;//归并while (begin1 <= end1 && begin2 <= end2){if (nums[begin1] > nums[begin2])temp[index++] = nums[begin2++];elsetemp[index++] = nums[begin1++];}while (begin1 <= end1)temp[index++] = nums[begin1++];while (begin2 <= end2)temp[index++] = nums[begin2++];//将temp暂时存储的数据覆盖待排序列nums原有位置的数据(拷贝回去),实现待排序列区间有序for (int i = left; i <= right; i++)nums[i] = temp[i];
}void MergeSort(int *nums, int numsSize)
{int* temp = (int*)malloc(sizeof(int) * numsSize);_MergeSort(nums, temp, 0, numsSize - 1);free(temp);                           
}

非递归

  • 我们可以直接用循环解决问题,如图所示:

在这里插入图片描述

  • 由上面的递归分析可以知道,两个单个数字可以直接合并成一个有序序列。因此我们定义gap,表示每次合并的两个序列长度为gap,gap从1递增,直到不能满足条件gap < numsSize,然后就进行和递归相同的合并操作就可以了
void MergeSort(int* nums, int numsSize)
{int* temp = (int*)malloc(sizeof(int) * numsSize);int gap = 1;while (gap < numsSize){/*因为每次是对两个有序序列进行合并因此每次合并过后i应该跳过两个序列长度*/for (int i = 0; i < numsSize; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;int index = begin1;//归并while (begin1 <= end1 && begin2 <= end2){if (nums[begin1] < nums[begin2])temp[index++] = nums[begin1++];elsetemp[index++] = nums[begin2++];}while(begin1 <= end1)temp[index++] = nums[begin1++];while(begin2 <= end2)temp[index++] = nums[begin2++];//将temp暂时存储的数据覆盖待排序列nums原有位置的数据(拷贝回去),实现待排序列区间有序for (int j = i; j <= end2; j++)nums[j] = temp[j];}gap *= 2;}free(temp);
}

处理边界情况

  • 看起来好像很简单,但上面的代码仍存在些许bug,仍需要我们谨慎处理
  • 我们来看一个情况,如果给的待排数组是{5,4,3,2,9,7,1,6,8}

在这里插入图片描述

  • 如果给的待排数组是{5,4,3,2,9,7

在这里插入图片描述

int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
int index = begin1;/*如果右半区间不存在,只有左半区间说明待合并的只有一个区间显然没有合并的必要,直接退出合并循环即可
*/
if (begin2 >= numsSize)break;//如果右半区间算多了,那么对end2进行修正
if (end2 >= numsSize)end2 = numsSize - 1;

实现代码

void MergeSort(int* nums, int numsSize)
{int* temp = (int*)malloc(sizeof(int) * numsSize);int gap = 1;while (gap < numsSize){/*因为每次是对两个有序序列进行合并因此每次合并过后i应该跳过两个序列长度*/for (int i = 0; i < numsSize; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;int index = begin1;/*如果右半区间不存在,只有左半区间说明待合并的只有一个区间显然没有合并的必要,直接退出合并循环即可*/if (begin2 >= numsSize)break;//如果右半区间算多了,那么对end2进行修正if (end2 >= numsSize)end2 = numsSize - 1;//归并while (begin1 <= end1 && begin2 <= end2){if (nums[begin1] < nums[begin2])temp[index++] = nums[begin1++];elsetemp[index++] = nums[begin2++];}while(begin1 <= end1)temp[index++] = nums[begin1++];while(begin2 <= end2)temp[index++] = nums[begin2++];//将temp暂时存储的数据覆盖待排序列nums原有位置的数据(拷贝回去),实现待排序列区间有序for (int j = i; j <= end2; j++)nums[j] = temp[j];}gap *= 2;}free(temp);
}

时间复杂度

  • 易得,合并两个有序序列的时间复杂度为O(N)
  • 由于是对待排序列的不断二分,知道分割为不可分割的子序列,因此这一过程的时间复杂度为O(log2N)
  • 因此归并排序的时间复杂度为O(NlogN)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/1766.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《C++ Primer》--学习7

顺序容器 容器库概览 迭代器 与容器一样&#xff0c;迭代器有着公共的接口&#xff1a;如果一个迭代器提供某个操作&#xff0c;那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的。 迭代器范围 一个迭代器范围是由一对迭代器表示&#xff0c;两个迭代器分别指向…

IM即时通讯APP在聊天场景中的应用

即时通讯&#xff08;IM&#xff09;应用可以满足人们随时随地进行文字、语音、图片、视频等多媒体信息的传递需求&#xff0c;为个人和企业提供了高效、便捷的沟通方式。在企业中&#xff0c;IM即时通讯APP更是发挥着重要的作用&#xff0c;促进了协作和团队工作的效率提升。以…

项目——学生信息管理系统3

目录 班级添加的界面实现 创建班级的实体类 在org.xingyun.dao 包下 编写 ClassDao 创建 AddStudentClassFrm 添加班级页面 注意创建成 JInternalFrame 类型 给控件起个名字 注释掉main方法 给提交按钮绑定事件 回到 MainFrm.java 给添加班级按钮绑定事件 启动测试 班…

基于卡尔曼滤波进行四旋翼动力学建模(SimulinkMatlab)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

网络安全实战植入后门程序

在 VMware 上建立两个虚拟机&#xff1a;win7 和 kali。 Kali&#xff1a;它是 Linux 发行版的操作系统&#xff0c;它拥有超过 300 个渗透测试工具&#xff0c;就不用自己再去找安装包&#xff0c;去安装到我们自己的电脑上了&#xff0c;毕竟自己从网上找到&#xff0c;也不…

通俗易懂,十分钟读懂DES,详解DES加密算法原理,DES攻击手段以及3DES原理。Python DES实现源码

文章目录 1、什么是DES2、DES的基本概念3、DES的加密流程4、DES算法步骤详解4.1 初始置换(Initial Permutation&#xff0c;IP置换)4.2 加密轮次4.3 F轮函数4.3.1 拓展R到48位4.3.2 子密钥K的生成4.3.3 当前轮次的子密钥与拓展的48位R进行异或运算4.3.4 S盒替换&#xff08;Sub…

Object counting——生成密度图density map

文章目录 过程代码参考 过程 首先构造一个和原始图片大小相同的矩阵&#xff0c;并将其全部置为0&#xff0c;然后将每个被标记的人头对应的位置置为1&#xff0c;这样就得到了一个只有0和1的矩阵&#xff0c;最后通过高斯核函数进行卷积得到一个连续的密度图。 代码 import…

CSS知识点汇总(十)--移动端适配

文章目录 怎么做移动端的样式适配&#xff1f;1、方案选择2. iPhoneX 适配方案 怎么做移动端的样式适配&#xff1f; 在移动端虽然整体来说大部分浏览器内核都是 webkit&#xff0c;而且大部分都支持 css3 的所有语法。但手机屏幕尺寸不一样&#xff0c;分辨率不一样&#xff0…

架构-嵌入式模块

章节架构 约三分&#xff0c;主要为选择题 #mermaid-svg-z6RGCDSEQT5AhE1p {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-z6RGCDSEQT5AhE1p .error-icon{fill:#552222;}#mermaid-svg-z6RGCDSEQT5AhE1p .error-text…

Pytorch--模型微调finetune--迁移学习 (待继续学习)

https://www.bilibili.com/video/BV1Z84y1T7Zh/?spm_id_from333.788&vd_source3fd64243313f29b58861eb492f248b34 主要方法 torchvision 微调timm 微调半精度训练 背景&#xff08;问题来源&#xff09; 解决方案 大模型无法避免过拟合&#xff0c;

深度学习之目标检测R-CNN模型算法流程详解说明(超详细理论篇)

1.R-CNN论文背景 2. R-CNN算法流程 3. R-CNN创新点 一、R-CNN论文背景 论文网址https://openaccess.thecvf.com/content_cvpr_2014/papers/Girshick_Rich_Feature_Hierarchies_2014_CVPR_paper.pdf   RCNN&#xff08;Region-based Convolutional Neural Networks&#xff…

Linux信号概念、认识、处理动作 ( 2 ) -【Linux通信架构系列 】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the…