数据结构——lesson12排序之归并排序

💞💞 前言

hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹
在这里插入图片描述

💥个人主页:大耳朵土土垚的博客
💥 所属专栏:数据结构学习笔记 、排序算法合集
💥对于数据结构顺序表、链表、堆以及排序有疑问的都可以在上面数据结构专栏和排序合集专栏进行学习哦~ 有问题可以写在评论区或者私信我哦~

前面我们学习过六种排序——直接插入排序、希尔排序、直接选择排序、堆排序、冒泡排序和快速排序,今天我们就来学习归并排序🥳🎉🎉🎉

1.归并排序

基本思想: 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并排序核心步骤:
在这里插入图片描述
归并排序的步骤类似于二叉树的后序遍历,先一直分解到不能再分,然后对两个子序列合并排序,最终得到全部排序好的序列;在这里插入图片描述

1.1归并排序(递归版)

在上图中我们看到它把序列拿下来排好后再放回原序列,归并排序需要在内存中重新开辟一个数组来存放排好的子序列,然后再拷贝回原数组,这样可以防止在原数组中操作困难以及很多错误的发生;
步骤:
①使用malloc函数开辟一个大小为原数组的空间存放在tmp中;
②重新构造递归函数(因为如果在原来的函数中递归,那么每次都会malloc开辟一个数组,不合适)_mergesort();
③分解数列,进行递归,创建mid遍量,从中间开始分割;
④当只有一个数时就不再分割(也就是begin>=end时);
⑤对子序列进行归并排序;

void _mergesort(int* a,int begin,int end, int* tmp)
{
	//递归结束条件
	if (begin >= end)
		return;
	//分左右区间
	
	int mid = (begin + end) / 2;
	_mergesort(a, begin, mid,tmp);
	_mergesort(a, mid+1, end,tmp);
	//归并排序
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1];
			begin1++;
		}
		else
		{
			tmp[i++] = a[begin2];
			begin2++;
		}
	}
	//如果最后begin1的序列有剩余
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	//如果最后begin2的序列有剩余
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	//最后再拷贝回原数组
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));

}

// 归并排序递归实现
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	
	_mergesort(a,0,n-1,tmp);
	free(tmp);//释放内存空间
}

结果如下:
上面一排是排序前,下面一排是用归并排序排完序后
在这里插入图片描述

1.2归并排序(非递归版)

归并排序非递归版主要是通过循环来实现两两归并;
步骤如下:
①使用malloc开辟tmp数组来存放归并好的数;
②创建gap来设定每次归并的序列的范围
③利用while循环来实现整个序列的多次归并;
while循环内部与递归的归并对子序列排序类似,不同的是需要嵌套for循环来实现多个gap范围的序列归并(因为此时已经将整个序列分成每gap个为一组,所以需要for循环来控制,使得这些序列全部都两两归并);
⑤归并完了后要使用memcpy函数将数据拷贝回原数组;

// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);//开辟数组来归并
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;//定义每次归并范围
	
	while (gap < n)//gap不能==n,因为此时?
	{
		int j = 0;//记录tmp数组下标
		//每次距离为gap的两组归并
		for (int i = 0; i < n; i+=2*gap)
		{
			int begin1 = i;
			int end1 = i + gap - 1;
			int begin2 = i + gap;
			int end2 = i + 2 * gap - 1;
			if (end1 >= n || begin2 >= n)
			{
				end1 = n - 1;
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			//归并
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[j++] = a[begin1];
					begin1++;
				}
				else
				{
					tmp[j++] = a[begin2];
					begin2++;
				}
			}
			//如果最后begin1的序列有剩余
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			//如果最后begin2的序列有剩余
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			memcpy(a + i, tmp + i, sizeof(int)*(end2 - i + 1));
		}
		gap *= 2;
	}
	free(tmp);
}

结果如下:
在这里插入图片描述

✨✨这里要注意两点:
🥳🥳(1)将数组每gap为一组分完后进行两两归并时,可能出现三种情况:
💥 ①到最后可能第一个序列存在,下一个序列不存在也就是begin2>=n的情况;
💥②还可能出现第一个序列只有一部分也就是end1>=n的情况;
💥 ③第二个序列只有一部分存在也就是end2>=n的情况;
出现①②两种情况说明最后一组只有一组或半组,这时不需要再归并,将end2 = n -1,并break跳出循环即可;
情况③有两组可以归并,但是第二组不完整,所以此时需要将end2 = n-1,不跳出循环继续归并即可;
🥳🥳(2)memcpy将tmp中归并的数拷贝回原数组时;
💥①可以考虑在for循环内部每次归并完两个序列后拷贝回去(上述代码就是使用这种)此时:
memcpy(a + i, tmp + i, sizeof(int)*(end2 - begin1 + 1))
要注意拷贝的位置要+i,并且拷贝的个数也应该是归并的两个序列的长度,(但是不能使用2*gap因为会出现上面的(1)问题,有可能序列不存在或部分存在)所以序列长度应该是end2 - i;
💥②可以在每次完整归并完距离为gap的序列后再进行拷贝,此时:
memcpy(a, tmp, sizeof(int) * n);
如下图所示:
此时不需要考虑拷贝的位置,直接全部拷贝即可;
在这里插入图片描述
??真的以为这么简单吗???不可能,绝对不可能😭😭
刚刚将十个数据0,1,2,3,4,5,6,7,8,9改成九个数据1,2,3,4,5,6,7,8,9结果如下:
在这里插入图片描述
可恶又错了,得重新捋一遍:
现在我们是要等for循环一遍将间距为gap的组两两归并,直到全部归并完再进行拷贝,之前是for循环内部每两组归并完就拷贝,那为什么全部归并完再拷贝会出错呢???
捋一遍发现:
if (end1 >= n || begin2 >= n) { end1 = n - 1; break; }
这里有问题,在只有一个序列或半个序列时,我们直接跳出了循环,也就是此时原数组后面的数根本没有输入到tmp数组里面,此时tmp后面的数是不知道是什么的,然后你再全部一拷贝回原数组,这样不仅不能排序,还会将原数组的数给覆盖掉,所以如果要使用: memcpy(a, tmp, sizeof(int) * n);
我们不能再次使用上面的if语句,需要改一改:

if (end1 >= n || begin2 >= n)
{
	end1 = n - 1;
	//break
	//不能跳出循环,需要将序列1的数录入到tmp数组中,所以只要设置序列2不存在即可
	//要是序列2不存在,只需begin2 > end2 即可
	begin2 = i + 1;
	end2 = i;
}

结果如下:
在这里插入图片描述
此时就可以使用 memcpy(a, tmp, sizeof(int) * n);啦🥳🥳🥳

注意不可以在跳出break循环后再进行拷贝哦~因为这样没办法知道之前归并好的数据是什么,所以没办法进行归并排序💫💫

2.归并排序复杂度分析

2.1空间复杂度

无论递归还是非递归,我们都使用malloc函数开辟了tmp数组,大小是n,所以它的空间复杂度是O(n);🥳🥳🥳

2.2时间复杂度

我们可以利用非递归的来看归并排序的时间复杂度:
①首先,无论gap是什么,都需要借助for循环来遍历一遍数组进行归并排序每一遍都是n;
②所以只需要确定while循环多少次即可,有因为while循环条件是gap < n,每次gap*=2;
③所以while循环log(n)次所以归并排序非递归的时间复杂度是O(nlogn);
而非递归是利用while循环对递归的另一种表现形式,它们的原理逻辑都是一样的,所以递归版的时间复杂度也应该是O(n
logn);🥳🥳🥳

3.结语

我们学习了归并排序的两种实现——递归与非递归版;并分析了归并排序的时间和空间复杂度,以上就是归并排序的所有内容啦~ 完结撒花~🥳🎉🎉🎉

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++2D原创我的世界1.00.3版本上市!!!

我很郁闷&#xff0c;为什么就是整不了昼夜交替啊喂&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 虽然这看上去很简单&#xff0c;但做起来要我命&#xff01;&#xff01;&#xff01; 优化过后总共1312行&#xff0c…

Linux:内核源代码角度看文件和Socket

文章目录 文件和Socket 文件和Socket 在之前写的网络服务&#xff0c;它们的本质其实就是一个进程&#xff0c;而对于每一个打开的文件来说&#xff0c;都要有一个自己对应的文件描述符&#xff0c;其中会默认打开对应的012&#xff0c;作为标准输入标准输出标准错误&#xff…

数据结构——lesson13排序之计数排序

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

如何简化多个 if 的判断结构

多少算太多&#xff1f; 有些人认为数字就是一&#xff0c;你应该总是用至少一个三元运算符来代替任何单个 if 语句。我并不这样认为&#xff0c;但我想强调一些摆脱常见的 if/else 意大利面条代码的方法。 我相信很多开发人员很容易陷入 if/else 陷阱&#xff0c;不是因为其…

ThreadLocal的基本使用

一、ThreadLocal的介绍 ThreadLocal 是 Java 中的一个类&#xff0c;它提供了线程局部变量的功能。线程局部变量是指每个线程拥有自己独立的变量副本&#xff0c;这些变量在不同的线程中互不影响。ThreadLocal 提供了一种在多线程环境下&#xff0c;每个线程都可以独立访问自己…

PS从入门到精通视频各类教程整理全集,包含素材、作业等(4)

PS从入门到精通视频各类教程整理全集&#xff0c;包含素材、作业等 最新PS以及插件合集&#xff0c;可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制&#xff0c;今天先分享到这里&#xff0c;后续持续更新 PS人物数码照片处理技法视频教程 https://www.…

武汉星起航:一站式跨境电商服务引领者,专业高效助力客户出海

武汉星起航电子商务有限公司&#xff0c;坐落于华中地区的商业核心地带——湖北武汉&#xff0c;自公司成立以来&#xff0c;便以提供一站式跨境电商服务为核心发展&#xff0c;致力于为广大客户提供专业、高效、全面的出海解决方案。凭借5对1服务体系、ERP软件授权、中转仓服务…

二、分布式事务

目录 二、分布式事务2.1 什么是分布式事务2.2 分布式事务产生的背景2.3 分布式事务产生的场景2.4 分布式事务理论4.1 CAP理论4.2 Base理论 5、分布式事务的解决方案 二、分布式事务 2.1 什么是分布式事务 一组操作会产⽣多个数据库session会话 此时就会出现分布式事务 2.2 分…

游戏软件出现d3dcompiler_47.dll缺失怎么修复,亲测的六种有效方法推荐

D3DCompiler47.dll是DirectX SDK中的一个重要组件&#xff0c;它提供了将HLSL&#xff08;High-Level Shading Language&#xff09;着色器编译为可执行代码的功能。通过使用D3DCompiler47.dll&#xff0c;开发人员可以将复杂的着色器代码转换为可以在GPU上高效运行的机器代码&…

黑马点评项目笔记 II

基于Stream的消息队列 stream是一种数据类型&#xff0c;可以实现一个功能非常完善的消息队列 key&#xff1a;队列名称 nomkstream&#xff1a;如果队列不存在是否自动创建&#xff0c;默认创建 maxlen/minid&#xff1a;设置消息队列的最大消息数量 *|ID 唯一id&#xff1a;…

Vue系列-el挂载

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>el:挂载点</title> </head> <body&g…

作业 二维数组-定位问题

图形相似度 描述 给出两幅相同大小的黑白图像&#xff08;用0-1矩阵&#xff09;表示&#xff0c;求它们的相似度。 说明&#xff1a;若两幅图像在相同位置上的像素点颜色相同&#xff0c;则称它们在该位置具有相同的像素点。 两幅图像的相似度定义为相同像素点数占总像素点数…

P87 4.1 C++ FOR 与Delphi FOR 的区别

输出x, sin(x), cos(x), tan(x)的值。已知X0&#xff0c;10&#xff0c; 20&#xff0c;180。 我用Delphi编写了程序&#xff1a; 第10行出现 给FOR 循环变量赋值i错误。 C中是可以的&#xff0c; 详见&#xff1a;delphi循环的一个小知识_assignment to for-loop variable…

安装JupyterLab的集成环境

Python集成环境安装 不要半途而废&#xff0c;不要作业太多就抛下你手中的笔&#xff0c;拿起你旁边的手机&#xff0c;你觉得这样很有意义吗&#xff1f;一个小时一道题都没做&#xff0c;盯着手机屏幕它能给你一个未来吗&#xff1f;少分心就能多做一道题&#xff0c;多学样本…

Java多线程:定位死锁

检测死锁可以使用jconsole工具&#xff0c;或使用jps定位进程id&#xff0c;再用jstack定位死锁 方案1&#xff1a; 1. 先用jps查看所有的java进程id 2. jstack 进程id定位死锁 3. 查看死锁结果 方案2:从jdk的安装路径中找到bin目录, 点击jconsole

Kafka入门到实战-第五弹

Kafka入门到实战 Kafka常见操作官网地址Kafka概述Kafka的基础操作更新计划 Kafka常见操作 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://kafka.apache.org/Kafka概述 Apache Kafka 是一个开源的分布式事件流平台&…

1.5编写一个程序,输入梯形的上底,下底和高,输出梯形的面积。

1、编写一个程序,输入梯形的上底,下底和高,输出梯形的面积。 package com.kangning.web.controller.system;import java.util.Scanner;/*** 编写一个程序,输入梯形的上底,下底和高,输出梯形的面积。*/ public class CountArea {public static void main(String[] args) …

知乎:多云架构下大模型训练,如何保障存储稳定性?

知乎&#xff0c;中文互联网领域领先的问答社区和原创内容平台&#xff0c;2011 年 1 月正式上线&#xff0c;月活跃用户超过 1 亿。平台的搜索和推荐服务得益于先进的 AI 算法&#xff0c;数百名算法工程师基于数据平台和机器学习平台进行海量数据处理和算法训练任务。 为了提…

java入门学习Day01

本篇文章主要是学会如何使用IDEA&#xff0c;和运行第一个java文件。 java环境安装&#xff1a;Windows下Java环境配置教程_windows java环境配置-CSDN博客 IDEA安装&#xff1a;IDEA 2023.2.5 最新激活码,注册码&#xff08;亲测好用&#xff09; - 异常教程 以上两个链接…

函数栈帧的创建与销毁(最详细的一集)上

前言 1.我们在进行c语言代码编程的时候&#xff0c;常常会把独立的一个功能抽象为函数&#xff0c;利用函数去实现各种的功能&#xff0c;那么&#xff0c;函数是如何调用的&#xff1f;函数的返回值是怎么返回的&#xff1f;参数又是如何传参的&#xff1f;所有这些问题都会跟…