归并排序 merge Sort + 图解 + 递归 / 非递归

  1. 归并排序(merge sort)的主要思想是:将若干个有序序列逐步归并,最终归并为一个有序序列
  2. 二路归并排序(2-way merge sort)归并排序最简单的排序方法

(1)二路归并排序的递归实现

// 二路归并排序的递归实现
void merge(vector<int>& arr,int left, int mid, int right) {int n = right - left + 1;vector<int> help(n, 0);int i = 0,a = left, b = mid + 1;while (a <= mid && b <= right) {help[i++] = arr[a] <= arr[b] ? arr[a++] : arr[b++];}	while (a <= mid) { // 对第一个子序列进行收尾工作help[i++] = arr[a++];}while (b <= right) { // 对第二个子序列进行收尾工作help[i++] = arr[b++];}for (int i = 0; i < n; i++) {arr[i+ left] = help[i];}
}void mergeSort(vector<int>& arr,int left, int right) {if (left == right) return;// 只有1个记录,递归结束int mid = (left + right) / 2;//int mid = left + ((right - left) >> 1);mergeSort(arr,left, mid);//归并排序前半个子序列mergeSort(arr,mid + 1, right);//归并排序后半个子序列merge(arr,left, mid, right);//将两个已排序的子序列合并
}

二路归并排序需要进行 \left \lceil log2^{n} \right \rceil合并两个子序列的时间性能为O(n)。因此,二路归并排序的时间复杂度是O(nlog2^{n}),这是归并排序算法最好,最坏,平均的时间性能。

二路归并排序在归并过程中需要与待排序序列同样数量的存储空间,空间复杂度为O(n)。二路归并排序是一种稳定的排序方法。

总结:归并排序递归实现是一种自顶向下的方法,形式简洁但效率相对较差。


(2)二路归并排序的非递归实现

分析二路归并排序的递归执行过程,如图所示,可以将具有 n 个记录的待排序序列看成是 n 个长度为 1 的有序子序列,然后进行两两合并,得到 \left \lceil \frac{n}{2} \right \rceil个长度为 2 的有序子序列(最后一个有序序列的长度可能是1),再进行两两归并,得到 \left \lceil \frac{n}{4} \right \rceil个长度为 4 的有序子序列(最后一个有序序列的长度可能小于4),以此类推,直至得到一个长度为 n 的有序序列

二路归并排序的非递归实现需要解决的关键问题是:

  • ① 如何构造初始的有序子序列?
  • ② 如何实现有序子序列的两两合并从而完成一趟归并?
  • ③ 如何控制二路归并的结束?

问题①的解决:设待排序序列含有 个记录,则可将整个序列看成是 个长度为 1 的有序子序列

问题②的解决:在一趟归并中,除最后一个有序序列外,其他有序序列中记录的个数(称为序列长度)相同,用 h 表示。现在的任务是把若干个相邻的长度为 h 的有序序列和最后一个长度有可能小于 h 的有序序列进行两两合并,将结果存放到 help[n] 中,为此,设参数 i 指向待归并序列的第一个记录。初始时 i = 0,显然合并的步长应该是 2h。在归并过程中,有以下三种情况:

  • 情况一:若 i + 2h <= n,表示待合并的两个相邻的有序子序列的长度均为 h ,如下图所示:执行一次合并,完成后 i 2h,准备进行下一次合并

  • 情况二:若 i + h < n,则表示仍有两个相邻有序子序列,一个长度为 h ,另一个长度小于 h,如下图所示:执行这两个有序序列的合并,完成后退出一趟归并

  • 情况三:若 i + h >= n,则表明只剩下一个有序子序列,如下图所示,不用合并 

综上,一趟归并排序的成员函数定义如下:

void mergePass(vector<int>& arr,int h,int n) {int i = 0;while (i + 2 * h <= n) { // 有两个长度为 h 的子序列merge(arr,i, i + h - 1, i + 2 * h - 1);i = i + 2 * h;}if (i + h < n) { // 两个子序列一个长度小于hmerge(arr,i, i + h - 1, n - 1);}
}

问题③的解决:开始时,有序子序列的长度为1,结束时,有序子序列的长度为 n。因此,可以用有序子序列的长度来控制排序过程的结束。二路归并排序非递归算法的成员函数定义如下:

void mergeSort2(vector<int>& arr,int n) {int h = 1;while (h < n) { // 两个子序列一个长度小于hmergePass(arr,h, n); // 一趟归并排序h = 2 * h;}
}

总结:归并排序非递归实现是一种自底向上的方法,算法效率较高,但算法较为复杂。


(3)C++完整代码: 

/*归并排序(merge sort)的主要思想是:将若干个有序序列逐步归并,最终归并为一个有序序列二路归并排序(2-way merge sort)是归并排序中最简单的排序方法we
*/
#include <iostream>
#include <vector>
using namespace std;// 二路归并排序的递归实现
void merge(vector<int>& arr,int left, int mid, int right) {int n = right - left + 1;vector<int> help(n, 0);int i = 0,a = left, b = mid + 1;while (a <= mid && b <= right) {help[i++] = arr[a] <= arr[b] ? arr[a++] : arr[b++];}	while (a <= mid) { // 对第一个子序列进行收尾工作help[i++] = arr[a++];}while (b <= right) { // 对第二个子序列进行收尾工作help[i++] = arr[b++];}for (int i = 0; i < n; i++) {arr[i+ left] = help[i];}
}void mergeSort(vector<int>& arr,int left, int right) {if (left == right) return;// 只有1个记录,递归结束int mid = (left + right) / 2;//int mid = left + ((right - left) >> 1);mergeSort(arr,left, mid);//归并排序前半个子序列mergeSort(arr,mid + 1, right);//归并排序后半个子序列merge(arr,left, mid, right);//将两个已排序的子序列合并
}void mergePass(vector<int>& arr,int h,int n) {int i = 0;while (i + 2 * h <= n) { // 有两个长度为 h 的子序列merge(arr,i, i + h - 1, i + 2 * h - 1);i = i + 2 * h;}if (i + h < n) { // 两个子序列一个长度小于hmerge(arr,i, i + h - 1, n - 1);}
}void mergeSort2(vector<int>& arr,int n) {int h = 1;while (h < n) { // 两个子序列一个长度小于hmergePass(arr,h, n); // 一趟归并排序h = 2 * h;}
}
int main() {vector<int> arr = { 6,4,2,3,9,1,4 };int n = arr.size();//mergeSort(arr, 0, n - 1);mergeSort2(arr,n);for (int i = 0; i < n; i++) {cout << " " << arr[i] << " " << endl;}system("pause");return 0;
}

本文参考书籍:数据结构----从概念到C++实现(第三版)王红梅、王慧、王新颖 编著

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

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

相关文章

STM32F4X SDIO(九) 例程讲解-SD卡擦除、读写

STM32F4X SDIO &#xff08;九&#xff09; 例程讲解-SD卡擦除、读写 例程讲解-SD卡擦除、读写SD卡擦除CMD32:ERASE_WR_BLK_START命令发送命令响应 CMD33:ERASE_WR_BLK_END命令发送命令响应CMD38:ERASE命令响应 CMD13:SD_CMD_SEND_STATUS命令发送命令回应 SD卡读数据CMD16:SET_…

[Mac软件]Adobe Media Encoder 2024 V24.0.2免激活版

软件说明 使用Media Encoder&#xff0c;您将能够处理和管理多媒体。插入、转码、创建代理版本&#xff0c;并几乎以任何可用的格式输出。在应用程序中以单一方式使用多媒体&#xff0c;包括Premiere Pro、After Effects和Audition。 紧密整合 与Adobe Premiere Pro、After …

Spring Boot (三)

1、热部署 热部署可以替我们节省大把花在重启项目本身上的时间。热部署原理上&#xff0c;一个springboot项目在运行时实际上是分两个过程进行的&#xff0c;根据加载的东西不同&#xff0c;划分成base类加载器与restart类加载器。 base类加载器&#xff1a;用来加载jar包中的类…

【电工基础】

电工基础 11.1 简介1.2 电路作用1.3 电路模型1.4 电流定义1.5 电压定义1.6 电动势1.7 电阻元件1.7.1 电阻元件定义1.7.2 电阻原件的特性1.7.31.7.4 1.81.91.10 345 1 1.1 简介 电源外部&#xff0c;正电荷移动的方向是由电源正极向电源负极方向&#xff0c;负电荷移动的方向是…

永达理简析:利用保险的“财务规划”功能维持退休后生活水平

现代社会环境背景下&#xff0c;“自养自老”已经是一种未来养老趋势&#xff0c;很多人会为自己准备一份长期、比较周全的保障&#xff0c;这样财务规划不仅会分担子女的压力&#xff0c;也让自己有一个长远的保障。在各种财务储蓄工具中&#xff0c;商业保险占据着不可取代的…

一台电脑使用多个gitee账号,以及提交忽略部分文件

目录 ​编辑 一&#xff1a;前言 二&#xff1a;解决方法 三&#xff1a;提交gitee时忽略文件 一&#xff1a;前言 在开发中&#xff0c;我们拥有不止一个 gitee 账号&#xff0c;通常而言一个是公司的&#xff0c;一个是私人的。有时候我们在公司写了一些自己的东西&#…

CSS3 过度效果、动画、多列

一、CSS3过度&#xff1a; CSS3过渡是元素从一种样式逐渐改变为另一种的效果。要实现这一点&#xff0c;必须规定两相内容&#xff1a;指定要添加效果的CSS属性&#xff1b;指定效果的持续时间。如果为指定持续时间&#xff0c;transition将没有任何效果。 <style> div…

OpenCV(opencv_apps)在ROS中的视频图像的应用(重点讲解哈里斯角点的检测)

1、引言 通过opencv_apps&#xff0c;你可以在ROS中以最简单的方式运行OpenCV提供的许多功能&#xff0c;也就是说&#xff0c;运行一个与功能相对应的launch启动文件&#xff0c;就可以跳过为OpenCV的许多功能编写OpenCV应用程序代码&#xff0c;非常的方便。 对于想熟悉每个…

Scala中编写多线程爬虫程序并做可视化处理

目录 一、引言 二、Scala爬虫程序的实现 1、引入必要的库 2、定义爬虫类 3、可视化处理 三、案例分析&#xff1a;使用Scala爬取并可视化处理电影数据 1、定义爬虫类 2、实现爬虫程序的控制逻辑 3、可视化处理电影数据 四、总结 一、引言 随着互联网的快速发展&#…

Easyui DataGrid combobox联动下拉框内容

发票信息下拉框联动&#xff0c;更具不同的发票类型&#xff0c;显示不同的税率 专票 普票 下拉框选择事件 function onSelectType(rec){//选中值if (rec2){//普通发票对应税率pmsPlanList.pmsInvoiceTaxRatepmsPlanList.pmsInvoiceTaxRateT}else {//专用发票对应税率pmsPlan…

ObjectMapper - 实现复杂类型对象反序列化(天坑!)

目录 一、复杂类型反序列化 1.1、背景 1.2、问题解决 一、复杂类型反序列化 1.1、背景 a&#xff09;例如有 AppResult 对象&#xff0c;如下&#xff1a; Data public class AppResult {private Integer code;private String msg;private Object data;} b&#xff09;App…

C++ 开发【深入浅出】笔记02

多态 同一种类型的不同表现形式基类指针指向基类对象基类对象调用的成员函数&#xff0c;基类指针指向派生类对象则调用派生类得成员函数&#xff0c;这种现象就称为多态构成多态的条件 继承关系基类多态函数必须声明为虚函数&#xff08;virtual&#xff09;派生类必须覆盖&am…