算法 -归并排序

博客主页:【夜泉_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语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

相关推荐
curemoon19 分钟前
理解都远正态分布中指数项的精度矩阵(协方差逆矩阵)
人工智能·算法·矩阵
柃歌27 分钟前
【UCB CS 61B SP24】Lecture 7 - Lists 4: Arrays and Lists学习笔记
java·数据结构·笔记·学习·算法
柃歌37 分钟前
【UCB CS 61B SP24】Lecture 4 - Lists 2: SLLists学习笔记
java·数据结构·笔记·学习·算法
SKYDROID云卓小助手2 小时前
无人设备遥控器之如何分享数传篇
网络·人工智能·算法·计算机视觉·电脑
Lqingyyyy2 小时前
P2865 [USACO06NOV] Roadblocks G 与最短路的路径可重复的严格次短路
开发语言·c++·算法
WHATEVER_LEO2 小时前
【每日论文】Text-guided Sparse Voxel Pruning for Efficient 3D Visual Grounding
人工智能·深度学习·神经网络·算法·机器学习·自然语言处理
Vacant Seat3 小时前
贪心算法-买卖股票的最佳时机
java·数据结构·算法·贪心算法
郑州吴彦祖7723 小时前
数据结构——二叉树经典习题讲解
java·数据结构·算法·leetcode
lyx1426063 小时前
leetcode 8. 字符串转换整数 (atoi)
算法
qy发大财4 小时前
跳跃游戏II(力扣45)
算法·leetcode