【排序算法】 归并排序详解!深入理解!思想+实现!

news2024/12/22 23:34:59

在这里插入图片描述

🎥 屿小夏 : 个人主页
🔥个人专栏 : 算法—排序篇
🌄 莫道桑榆晚,为霞尚满天!

文章目录

  • 📑前言
  • 🌤️归并排序的思想
    • ☁️基本思想
    • ☁️归并的思想实现
    • ☁️分治法
  • 🌤️归并排序的实现
    • ☁️核心操作步骤
    • ☁️递归版归并实现
      • ⭐代码实现详解:
    • ☁️非递归版归并实现
      • ⭐代码实现详解:
  • 🌤️归并排序特性总结
  • 🌤️全篇总结

📑前言

​ 什么是归并?通过归并排序就能让数据有序?分治法是怎么在归并排序上应用的?本文将对归并排序进行细致入微的讲解,庖丁解牛般让你彻底明白归并排序!

🌤️归并排序的思想

☁️基本思想

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

☁️归并的思想实现

​ 将两个有序数组合并成一个有序数组的操作。在归并排序中,通过不断地将数组分成两半,分别对每一半进行排序,然后将排好序的两个子数组合并成一个有序数组,最终得到整个数组有序的结果。

☁️分治法

​ 分治法在归并排序中的应用是将问题分解成更小的子问题,然后分别解决这些子问题,最后将子问题的解合并起来得到原问题的解。具体来说,归并排序的分治法应用如下:

  1. 分解:将待排序的数组分成两个子数组,分别为左子数组和右子数组。
  2. 解决:递归地对左子数组和右子数组进行归并排序。
  3. 合并:将排好序的左子数组和右子数组合并成一个有序数组。

🌤️归并排序的实现

☁️核心操作步骤

静图全步骤概览:

在这里插入图片描述

动图一步步的逻辑实现:

在这里插入图片描述

☁️递归版归并实现

//归并
void _MergeSort(int* a, int* tmp, int begin, int end)
{
	if (end <= begin)
	{
		return;
	}
	int mid = (begin + end) / 2;
	_MergeSort(a, tmp, begin, mid);
	_MergeSort(a, tmp, mid + 1, end);
	// a->[begin, mid][mid+1, end]->tmp
	//归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int index = begin;

	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = a[begin2++];
	}
	memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
//归并(递归版)
void MergeSort(int* a, int n)
{
	int* tmp = malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	_MergeSort(a, tmp, 0, n - 1);
	free(tmp);
}

​ 归并需要开一个同样大的tmp数组来存放数据,相当于是拿空间来换时间了.

⭐代码实现详解:

  1. 首先,将整个序列分为两部分,分别递归调用_MergeSort函数对左右两部分进行排序。
  2. 在_MergeSort函数中,首先判断递归终止条件,如果end小于等于begin,则表示当前子序列只有一个元素或者为空,无需排序,直接返回。
  3. 然后,计算中间位置mid,并分别递归调用_MergeSort函数对左右两部分进行排序。
  4. 接下来,进行归并操作。首先,设置四个指针begin1、end1、begin2、end2分别指向左右两部分的起始和结束位置,以及一个指针index指向当前归并结果的位置。
  5. 然后,使用两个循环比较左右两部分的元素大小,并将较小的元素放入tmp数组中,同时移动相应的指针。
  6. 最后,将剩余的元素复制到tmp数组中。
  7. 最后,将tmp数组中的元素复制回原数组a中,完成归并排序。

☁️非递归版归并实现

​ 非递归版是在递归上进行了修改,将其递归改为了循环。

//归并(非递归版)
void _MergeSortNotR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			if (begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			memcpy(a+i, tmp+i, (end2-i+1) * sizeof(int));
		}
		gap *= 2;
	}
	free(tmp);
}

⭐代码实现详解:

  1. 分配一个临时数组tmp,用于存储归并结果。

  2. 设置一个变量gap为1,表示归并的间隔大小。

  3. 接下来,循环执行以下操作,直到gap大于等于n:

    1. 遍历整个数组,每次取两个相邻的子序列进行归并。
    2. 计算左右两个子序列的起始和结束位置。
    3. 判断右子序列的结束位置是否超过了数组的长度,如果超过,则将结束位置设置为数组的最后一个元素的下标。
    4. 使用两个指针begin1和begin2分别指向左右两个子序列的起始位置,使用指针end1和end2分别指向左右两个子序列的结束位置。
    5. 使用一个指针index指向当前归并结果的位置。
    6. 使用一个循环,比较左右两个子序列的元素大小,并将较小的元素放入临时数组tmp中,同时移动相应的指针。
    7. 如果左子序列还有剩余元素,则将剩余元素复制到tmp数组中。
    8. 如果右子序列还有剩余元素,则将剩余元素复制到tmp数组中。
    9. 将tmp数组中的元素复制回原数组a中。
    10. 将gap乘以2,进行下一轮归并。
  4. 最后,释放临时数组tmp的内存空间。

🌤️归并排序特性总结

  1. 稳定性:归并排序是一种稳定的排序算法,即相等元素的相对顺序在排序后不会改变。
  2. 时间复杂度:归并排序的时间复杂度是O(nlogn),其中n是待排序序列的长度。这是由于归并排序的核心操作是将序列分成两个子序列,然后分别进行排序,再将排序好的子序列合并,而分割和合并操作都需要O(logn)的时间,所以总的时间复杂度是O(nlogn)。
  3. 空间复杂度:归并排序的空间复杂度是O(n),其中n是待排序序列的长度。这是由于归并排序需要一个与待排序序列相同大小的额外空间来存储临时的合并结果。
  4. 非原地排序:归并排序不是原地排序算法,即它需要额外的空间来存储临时的合并结果。这是因为在合并操作中,需要同时访问两个子序列的元素,并将它们按照顺序合并到一个新的序列中。
  5. 递归实现:归并排序通常使用递归的方式来实现,即将待排序序列不断分割成更小的子序列,直到子序列的长度为1,然后再将这些子序列按照顺序合并成一个有序的序列。递归实现的归并排序代码简洁易懂,但是由于递归调用的开销比较大,所以在实际应用中可能会使用迭代的方式来实现归并排序。
  6. 适用性:归并排序适用于各种数据规模的排序,而且对于大规模数据的排序效果较好。它的时间复杂度稳定在O(nlogn),不会因为数据规模的增大而导致时间复杂度的增加。此外,归并排序还适用于外部排序,即对于无法一次性加载到内存的大规模数据进行排序。

🌤️全篇总结

​ 经过对归并排序的思想,特性,代码实现这些细致入微的讲解,相信聪明的你肯定已经学会了叭😊😊!

☁️ 好了,由于篇幅有限,本章只是对归并进行了深度解刨,后序会出更多的排序算法哦!
看到这里了希望给博主留个:
👍 点赞🌟收藏 ⭐️ 关注!
💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖
拜托拜托这个真的很重要!
你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

H5游戏源码分享-跳得更高

H5游戏源码分享-跳得更高 控制跳动踩到云朵上 <!DOCTYPE html> <html> <head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"><meta http-equiv"Content-Type" content"text/html;"&g…

利用ChatGPT进行股票走势分析

文章目录 1. 股票分析2. 技巧分析3. 分析技巧21. 股票分析 这张图片显示了一个股票交易软件的界面。以下是根据图片内容的一些解读: 股票代码: 图片右上角显示的代码是“600517”,这是股票的代码。 图形解读: 该图展示了股票的日K线图。其中,蜡烛图表示每日的开盘、收盘、最…

Rest风格基本语法与实战

1&#xff0c;前置知识点 1.1 GetMapping&#xff0c;PostMapping&#xff0c;PutMapping&#xff0c;DeleteMapping 平时我们都是使用RequestMapping&#xff0c;然后通过它的method属性来指定请求的方式&#xff0c;这样是有些麻烦的&#xff0c;然后这四个标签就是来简化这…

Day 16 python学习笔记

静态方法 静态方法&#xff0c;只需要在方法前添加 staticmethod&#xff0c; 方法就可以不写形参self&#xff0c;可以通过实例对象.方法名调用&#xff0c;也可以通过类名.方法名调用 例&#xff1a; class Test:staticmethoddef test1():print("你好")def nums(se…

异步 AIMD 收敛

给出的一直都是同步 AIMD 收敛&#xff0c;所以简单&#xff0c;但不至于 bbr 单流情形退化成简陋。 给出一个异步 AIMD 收敛过程是必要的&#xff0c;可见&#xff0c;它同样是简洁优美的&#xff1a; 虽然我没有标注太多&#xff0c;它始终没有成为一团乱麻。 和同步 AIM…

Linux tar打包命令

Linux 系统中&#xff0c;最常用的归档&#xff08;打包&#xff09;命令就是 tar&#xff0c;该命令可以将许多文件一起保存到一个单独的磁带或磁盘中进行归档。不仅如此&#xff0c;该命令还可以从归档文件中还原所需文件&#xff0c;也就是打包的反过程&#xff0c;称为解打…

随机链表的复制(Java详解)

一、题目描述 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的…

英语——分享篇——常用人物身份

常用人物身份 家庭成员类 father 父亲 mother 母亲 grandmother&#xff08;外&#xff09;祖母 grandfather&#xff08;外&#xff09;祖父 son 儿子 daughter 女儿 uncle 叔叔&#xff0c;舅舅 aunt 婶母&#xff0c;舅母 brother 兄弟 sister 姐妹 nephew 侄子 niece…

Centos7下生成https自签名证书

1、安装openssl yum install openssl2、生成带密码的私有秘钥文件 openssl genrsa -des3 -out server.key 2048使用带密码的私有秘钥文件时需要输入密码&#xff0c;这里直接输入&#xff1a;123456 3、生成不带密码的私有秘钥文件 openssl rsa -in server.key -out serve…

考虑时空相关性的风电功率预测误差MATLAB代码

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 风电功率预测置信区间误差分析拟合 1.风电功率预测误差--时空相关性 展示第一一个时间段的风电功率预测与实际风电功率值的比较。填充区域表示预测的不确定性&#xff0c;显示了95%置信区间内预测可能的范围…

vulnhub momentum 靶机复盘

环境配置 到学校了 原来的桥接配置要改 这里记录几个点备忘 1.virtualbox 桥接 未指定 重新安装驱动VBoxNetLwf.inf 2.配置完靶机启动失败 手动安装VBoxNetLwf.inf 不要用virtualbox自带的netlwfinstall 3.配置完nmap扫不到 rw init/bin/bash 进去看看网卡配置 信息收集…

【Java系列】HashMap

HashMap 介绍HashMap 类提供了很多有用的方法添加键值对(key-value)创建一个字符串&#xff08;String&#xff09;类型的 key 和字符&#xff08;String&#xff09;类型的 value&#xff1a;访问元素删除元素删除所有键值对(key-value)可以使用 clear 方法&#xff1a;计算大…

基于SSM的养老院管理系统

基于SSM的养老院管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatisVUE工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 摘要 养老院管理系统是一个基于SSM&#xff08;Spring、Spring MVC、MyBatis&…

基于SSM的航空订票系统

基于SSM的航空订票系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 用户界面 管理员界面 摘要 基于SSM的航空订票系统是一款面向旅客、航空公司和旅…

FreeRTOS_信号量之二值信号量

目录 1. 信号量简介 2. 二值信号量 2.1 二值信号量简介 2.1.1 二值信号量无效 2.1.2 中断释放信号量 2.1.3 任务获取信号量成功 2.1.4 任务再次进入阻塞态 2.2 创建二值信号量 2.2.1 vSemaphoreCreateBinary() 2.2.2 xSemaphoreCreateBinary() 2.2.3 xSemaphoreCrea…

初学编程需要什么基础,零基础学编程系统化编程课程之编程工具箱文件名称操作构件属性

初学编程需要什么基础&#xff0c;零基础学编程系统化编程课程之编程工具箱文件名称操作构件属性 上图为 该编程工具构件的基本属性和方法 编程系统化课程总目录及明细&#xff0c;零基础学中文编程视频教程&#xff0c;点击进入了解详情。 https://blog.csdn.net/qq_29129627/…

2023年阿里云双11有什么优惠活动?详细攻略来了!

随着双十一的临近&#xff0c;阿里云也正式开启了双11大促&#xff0c;推出了“金秋云创季”活动&#xff0c;那么&#xff0c;2023年阿里云双11的优惠活动究竟有哪些呢&#xff1f;本文将为大家详细介绍。 一、阿里云双11活动时间 1、2023年10月27日-2023年10月31日&#xff…

基于蝠鲼觅食算法的无人机航迹规划-附代码

基于蝠鲼觅食算法的无人机航迹规划 文章目录 基于蝠鲼觅食算法的无人机航迹规划1.蝠鲼觅食搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用蝠鲼觅食算法来优化无人机航迹规划。 …

前端HTML

文章目录 一、什么是前端前端后端 前端三剑客1.什么是HTML2.编写前端的步骤1.编写服务端2.浏览器充当客户端访问服务端​ 3.浏览器无法正常展示服务端内容(因为服务端的数据没有遵循标准)4.HTTP协议>>>:最主要的内容就是规定了浏览器与服务端之间数据交互的格式 3. 前…

“第五十五天”

定点数&#xff1a; 原码的乘法&#xff1a; 乘法的符号位是单独处理的&#xff08;通过对被乘数和乘数的符号位进行异或实现&#xff09;&#xff0c;数值位去绝对值进行运算。这里的乘法实际上是通过多次加法实现的。 这里被乘数是放在x寄存器&#xff0c;乘数放在MQ寄存器…