归并排序 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) { // 两个子序列一个长度小于h
		merge(arr,i, i + h - 1, n - 1);
	}
}

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

void mergeSort2(vector<int>& arr,int n) {
	int h = 1;
	while (h < n) { // 两个子序列一个长度小于h
		mergePass(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) { // 两个子序列一个长度小于h
		merge(arr,i, i + h - 1, n - 1);
	}
}

void mergeSort2(vector<int>& arr,int n) {
	int h = 1;
	while (h < n) { // 两个子序列一个长度小于h
		mergePass(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.niftyadmin.cn/n/5171246.html

相关文章

js 根据当前时间往前推15天或往后推15天等(例如当前时间往后15天后的日期。并实现今天、明天、后天、周)

本次分享&#xff0c;在项目中开发车票购买功能需要用到日期筛选 思路&#xff1a; 1、首先获取当前时间戳 2、根据当前时间戳拿到15天后的日期 3、根据天匹配星期几 4、将时间戳转换年、月、日并重组 实现代码 // 获取当前日期 const today new Date();// 往前推15天的…

vue3中的父子间传值

一、父传子 defineProps 父组件传值给子组件主要是由父组件为子组件通过v-bind绑定数值&#xff0c;而后传给子组件&#xff1b;子组件则通过defineProps接收使用。 <template><div class"fa"><div style"margin: 10px;">我是父组件&l…

C语言--输入10个数字,要求输出其中值最大的元素和该数字是第几个数

今天小编带大家了解一下什么是“打擂台”算法。 一.思路分析 可以定义一个数组arr&#xff0c;长度为10&#xff0c;用来存放10个数字&#xff0c;设计一个函数Max&#xff0c;用来求两个数中的较大值&#xff0c; 定义一个临时变量tmparr[0],保存临时最大的值&#xff0c;下标…

《LeetCode力扣练习》代码随想录——数组(螺旋矩阵II---Java)

《LeetCode力扣练习》代码随想录——数组&#xff08;螺旋矩阵II—Java&#xff09; 刷题思路来源于 代码随想录 59. 螺旋矩阵 II 左闭右开——[x,y) class Solution {public int[][] generateMatrix(int n) {if(n1){return new int[][]{{1}};}int[][] resultnew int[n][n];int…

保姆级自定义GPTs教程,无需任何代码!

11月10日&#xff0c;OpenAI正式宣布向所有ChatGPT Plus用户开放GPTs功能&#xff0c;一个人人都能开发自定义ChatGPT助手的时代降临。 GPTs支持无代码、可视化点击操作&#xff0c;这意味着即便你没有任何编程经验&#xff0c;只要有数据、脑洞大开的想法&#xff0c;就能开发…

迷雾系统-1 地图及其区块

创建UGUI地图,每块地块&#xff08;Image&#xff09;上添加AreaNode脚本&#xff0c;根据PolygonCollider2D可视化编辑碰撞体形状&#xff0c;并以此生成Mesh Mc_AreaNode脚本&#xff1a; private GameObject _objPrefab; //创建的Mesh预制体private float _canvasPosZ;pr…

网络监控系统和防火墙的区别有哪些?

现如今&#xff0c;市面上保护企业网络安全的设备有很多&#xff0c;其中使用最多的当属网络监控系统和防火墙。 网络监控系统就是通过网页内容的自动采集处理、敏感词过滤、智能聚类分类、主题检测、专题聚焦、统计分析等多个环节&#xff0c;实现相关网络舆情监督管理的需要…

LeetCode【207】课程表

题目&#xff1a; 思路&#xff1a; https://www.jianshu.com/p/25868371ddfc/ 代码&#xff1a; public boolean canFinish(int numCourses, int[][] prerequisites) {// 入度int[] indegress new int[numCourses];// 每个点对应的边,出边Map<Integer, List<Intege…