算法 -归并排序

博客主页:【夜泉_ly

本文专栏:【算法

欢迎点赞👍收藏⭐关注❤️

文章目录

  • [🔀 归并排序](#🔀 归并排序)
    • [📖 简介](#📖 简介)
    • [🖼️ 示意图](#🖼️ 示意图)
    • [💡 实现思路](#💡 实现思路)
    • [💻 代码实现](#💻 代码实现)
    • [💡 实现思路2 - 非递归](#💡 实现思路2 - 非递归)
    • [💻 代码实现](#💻 代码实现)

🔀 归并排序

📖 简介

  • 稳定 的排序
  • 时间复杂度 : O ( n log ⁡ n ) O(n \log n) O(nlogn)
  • 空间复杂度 : O ( n ) O(n) O(n),由于需要额外的临时数组来存储合并结果。
  • 常用于,处理 链表排序文件排序

🖼️ 示意图

归并排序 − 示意图 归并排序-示意图 归并排序−示意图

💡 实现思路

先来看看具体是怎么排的:
第一步 :先拿到需要整理的牌,将它们摊开。

第二步 :将这些牌分成较小的组,每组包含一张牌:

第三步 :对相邻的两组依次进行排序:

第四步 :把刚刚相邻的两组看做为一组:

第五步及以后 :重复上面的第三步、四步:


最后,由于只剩一组,排序结束。

怎么样,很简单吧 ~~

现在,我们来看看我们需要做什么。

在上面,看似用了很多步,其实很多步骤是重复的。

而重复,代表着可以试试递归。

总结一下:

  • 步骤一、分组
  • 步骤二、合并
  • 步骤三、判断是否结束

那么我们需要关注的是什么?

  • 一、怎么分组
  • 二、怎么合并
  • 三、看看什么时候结束

展开讲讲:

void merge_sort(int* arr, int left, int right)

一、怎么分?

那必须二分,想都不用想。

至于为什么?

首先是方便,其次是方便,然后是方便,

最后是效率稍高。

	int mid = (left + right) >> 1;

二、怎么合?

把上面的图抓下来:


这几张,就是分完后的样子。

我这里排的是升序,

可以看见,这里的每一组也都是升序:

这样看,合并就简单了。

因为我们只需要合并两个有序序列。

而如何保证这两个序列有序?

很简单,先递归:

	merge_sort(arr, left, mid);
	merge_sort(arr, mid + 1, right);

再合并:

	vector<int> tmp(right - left + 1);
	
	int l = left, r = mid + 1, cur = 0;
	while (l <= mid && r <= right) 
		tmp[cur++] = arr[l] <= arr[r] ? arr[l++] : arr[r++];
	while (l <= mid) tmp[cur++] = arr[l++];
	while (r <= right) tmp[cur++] = arr[r++];
	
	for (int i = 0; i < cur; i++) arr[i + left] = tmp[i];

递归至最后,长度变为 1 ,此时必为有序。

而如何保证最后一层长度为 1 ?

这就需要下面一步了:
三、怎么停?

看看传参:(int* arr, int left, int right)

嗯,长度 == right - left + 1;

那么我们想让长度大于等于 1。

即,我们不想长度小于 1:

	if (left >= right) return;

把这段放在函数开头,

然后

💻 代码实现

cpp 复制代码
void merge_sort(int* arr, int left, int right)
{
	// 递归结束条件
	if (left >= right) return;
	// 找到中间位置
	int mid = (left + right) >> 1;
	// 左右递归
	merge_sort(arr, left, mid);
	merge_sort(arr, mid + 1, right);
	// 辅助数组,存合并结果
	vector<int> tmp(right - left + 1);
	// 合并两个有序序列
	int l = left, r = mid + 1, cur = 0;
	while (l <= mid && r <= right) 
		tmp[cur++] = arr[l] <= arr[r] ? arr[l++] : arr[r++];
	while (l <= mid) tmp[cur++] = arr[l++];   // 将左侧剩余元素拷贝
	while (r <= right) tmp[cur++] = arr[r++]; // 将右侧剩余元素拷贝
	// 将合并结果拷回原数组
	for (int i = 0; i < cur; i++) arr[i + left] = tmp[i];
}

写的时候记得注意边界,

最好把上图红色部分画出来。

不熟的话,别只用脑子想,容易红温。

💡 实现思路2 - 非递归

刚刚的递归,帮我们简化了什么?

分组。

递归帮我们把组分好了。

那么非递归,我们还需要做什么?

分组。

怎么分?

为了保证每次的各组都是有序的,

最小的长度为 1 ,之后两两合并。

具体怎么做?

先把下面这个图画出来:

然后确定每组长度:

for (int len = 1; len < n; len *= 2) {

从一开始,每次乘二。
len < n,怎么来的?

很简单,len 是子数组的长度。

子数组是排好了的。

len >= n, 说明数组就排好了。

之后,确定两组的左边界:

for (int left = 0; left < n - len; left += 2 * len) {

有了左边界长度

就可以补全图中红字部分:

int mid = left + len - 1, right = min(n, left + 2 * len) - 1;
int l = left, r = mid + 1;

之后的合并,就和递归一样了。

最后补充说明一下边界情况:

一、left < n - len

唔。。。n - len 是为了放溢出。

便于理解的方式是这样写:left + len < n

倒过来看,就是 left + len >= n

说明了啥?

说明右边的数组不存在!

因此,继续循环的条件是 left < n - len

二、right = min(n, left + 2 * len) - 1

把上面的图稍稍改造,就能明白为什么这样写了:

💻 代码实现

cpp 复制代码
void merge_sort(int* arr, int n)
{
	vector<int> tmp(n);
	for (int len = 1; len < n; len *= 2)
	{
		for (int left = 0; left< n - len; left += 2 * len)
		{
			int mid = left + len - 1;
			int right = min(n, left + 2 * len) - 1;
			int l = left, r = mid + 1, cur = left;
			while (l <= mid && r <= right)
				tmp[cur++] = arr[l] <= arr[r] ? arr[l++] : arr[r++];
			while (l <= mid) tmp[cur++] = arr[l++];
			while (r <= right) tmp[cur++] = arr[r++];
			for (cur = left; cur <= right; cur++) 
				arr[cur] = tmp[cur];
		}
	}
}

希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!

本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

相关推荐
Felix_12151 小时前
2025 西电软工数据结构机考 Tip (By Felix)
算法
飞yu流星2 小时前
C++ 函数 模板
开发语言·c++·算法
pursuit_csdn2 小时前
力扣 74. 搜索二维矩阵
算法·leetcode·矩阵
labuladuo5203 小时前
洛谷 P8703 [蓝桥杯 2019 国 B] 最优包含(dp)
算法·蓝桥杯·动态规划
Milk夜雨3 小时前
C语言冒泡排序教程简介
数据结构·算法·排序算法
委婉待续4 小时前
redis的学习(三)
数据结构·算法
一直学习永不止步4 小时前
LeetCode题练习与总结:随机翻转矩阵--519
java·数学·算法·leetcode·哈希表·水塘抽样·随机化
xiao--xin4 小时前
LeetCode100之组合总和(39)--Java
java·开发语言·算法·leetcode·回溯
IT猿手5 小时前
部落竞争与成员合作算法(CTCM)求解5个无人机协同路径规划(可以自定义无人机数量及起始点),MATLAB代码
深度学习·算法·机器学习·matlab·无人机·无人机、
GISer_Jing5 小时前
React中 Reconciliation算法详解
前端·算法·react.js