【数据结构】-归并排序你真正学会了吗??

news2024/11/27 3:51:48

作者:小树苗渴望变成参天大树
作者宣言:认真写好每一篇博客
作者gitee:gitee
在这里插入图片描述
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、递归版本
  • 二、非递归版本
  • 三、总结


前言

今天我们再来将一个排序,这个排序,特别不好理解,尤其是非递归,递归版本还可以,它就是归并排序,我们知道两个有序的数组,通过第三个数组就会整体变成有序,数组中只有一个元素的时候南无这就是一个有序数组,我们利用这个思想就来进行归并,接下来就来听我讲解归并排序吧


一、递归版本

思想的铺垫:
我们先来看看两个有序的数组怎么通过第三个数组来变成·整体有序的吧
在这里插入图片描述
我们看到只要一个结束整体就结束,那个没有结束的再通过循环来赋值到第三个数组
在这里插入图片描述
我们一会就通过第三个数组就两个有序数组归并成整体有序的数组

归并排序的思想:

在这里插入图片描述
因为再递归的过程我们不知道这两个数组是否,只能保证递归到只有一个元素的时候是有序的,就不用递归,就就是递归结束的条件,并且不知道中途是否有序,我们只能把两个区间都递归到最后一层,先将左边有序,再将右边有序,最后再将根有序

但我们是在一个数组里面,怎么变成两个数组呢??不可能开辟两个数组把分开的,我们采取区间划分的方法即可,这样就是两个数组,我们通过第三个数组来变成有序,最后还是需要拷贝回原数组

那现在有一个问题,我们每次归并都要开辟数组进行排序吗??那样太麻烦了,我们通过定义一个子函数,进行归并,外面的函数开辟一次就够了,通过下表来控制,排序和拷贝回去应该什么那个位置的数据

我们来看函数整体:

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)//结束条件
		return;
	int mid = (left + right) / 2;//划分区间,为两个数组
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid+1, right, tmp);//类似为二叉树的后序遍历,因为要先递归到两个数组只有一个元素的时候,因为数组只有一个元素的时候肯定是有序的
	int begin1 = left; int end1 = mid;//左区间左右下标
	int begin2 = mid + 1; int end2 = right;//右区间左右下标
	int index = left;
	while (begin1 <= end1 && begin2 <= end2)//将两个有序数组通过第三个数组合并一个有序的
	{
		if (a[begin1] <= a[begin2])//左区间的值小,放到tmp数组
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];//右区间的值小,放到tmp数组
		}
	}
	while (begin1 <= end1)//左区间没结束
	{
		tmp[index++] = a[begin1++];
	}
	while (begin2 <= end2)//右区间没结束
	{
		tmp[index++] = a[begin2++];
	}
	for (int i = 0; i <index; i++)//拷贝回原数组
	{
		a[i] = tmp[i];
	}
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);//开辟一个和原数组一样的空间
	if(tmp == NULL)
	{
		perror("malloc fail");
	}
	_MergeSort(a,0,n-1,tmp);
	free(tmp);
}

我们仔细看看子函数里面的内容,有没有特别像二叉树里面的后序遍历,可能之前不懂,但是你看过代码之后肯定就会理解了,有的小伙伴当心,tmp拷贝回去不会有下标冲突的问题吗??这个完全不用担心,因为·我们再递归·的时候都把下标严格区分开,不会有第一个数组和第二个数组有交叉的下标,也不会有下标没有递归进去
在这里插入图片描述
希望大家可以好好理解,因为接下来将的非递归才是最难理解的

二、非递归版本

我们再递归的基础上,来想想,我们是通过直接递归到左右区间只有一个元素的时候,那我们不用递归,直接通过原数组来把区间先画分成只有一个元素不久好了,然后再划分成两个,四个,我们来看看图解:
在这里插入图片描述
我们根据这个图来些一下一趟的排序代码:

int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
	}
	int gap = 1; int j = 0;
		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;
			while (begin1 <= end1 && begin2 <= end2)//将两个有序数组通过第三个数组合并一个有序的
			{
				if (a[begin1] <= a[begin2])//左区间的值小,放到tmp数组
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];//右区间的值小,放到tmp数组
				}
			}
			while (begin1 <= end1)//左区间没结束
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)//右区间没结束
			{
				tmp[j++] = a[begin2++];
			}
		}
		for (int i = 0; i < n; i++)//拷贝回原数组
		{
			a[i] = tmp[i];
		}
		
	free(tmp);

我们来看一趟的运行结果
在这里插入图片描述
我们还要更新gap的值,所以我们外卖呢再套一个循环让gap走起来,来看一下完整代码:

void MergeSortRonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
	}
	int gap = 1;
	while (gap < n)
	{
		int j = 0;
		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;
			while (begin1 <= end1 && begin2 <= end2)//将两个有序数组通过第三个数组合并一个有序的
			{
				if (a[begin1] <= a[begin2])//左区间的值小,放到tmp数组
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];//右区间的值小,放到tmp数组
				}
			}
			while (begin1 <= end1)//左区间没结束
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)//右区间没结束
			{
				tmp[j++] = a[begin2++];
			}
		}
		for (int i = 0; i < n; i++)//拷贝回原数组
		{
			a[i] = tmp[i];
		}
		gap *= 2;//迭代
	}
	free(tmp);
}

注意: 相信大家看到这里就觉得结束了吗??非递归也不过如此嘛,真的是这样吗??我们再来测试一组数据:
在这里插入图片描述

我们发现出现断点错误,一般这种都是说明数组可能越界了,既然这样我们把每一组的下边打印出来看看

在这里插入图片描述
在这里插入图片描述
我们看到有的数据越界了,那这样我们就找到了问题所在,因为我们是最后整体把数据拷贝回去,所以我们比较把越界的下标修正,导到tmp数组里面,再一起导回原数组,如果归并一次就拷回去就不用担心这个问题,直接返回就好了我们来分析一下越界情况,来看图解:
在这里插入图片描述
我们再来看看修正后的前后对比:
在这里插入图片描述
最终非递归代码:

void MergeSortRonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
	}
	int gap = 1;
	while (gap < n)
	{
		int j = 0;
		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)//进行修正
			{
				end1 = n - 1;
				begin2 = n-1;
				end2 = n - 2;
			}
			else if (begin2 >= n)
			{
				begin2 = n-1;
				end2 = n - 2;
			}
			else if(end2>=n)
			{
				end2 = n - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)//将两个有序数组通过第三个数组合并一个有序的
			{
				if (a[begin1] <= a[begin2])//左区间的值小,放到tmp数组
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];//右区间的值小,放到tmp数组
				}
			}
			while (begin1 <= end1)//左区间没结束
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)//右区间没结束
			{
				tmp[j++] = a[begin2++];
			}
		}
		for (int i = 0; i < n; i++)//拷贝回原数组
		{
			a[i] = tmp[i];
		}
		gap *= 2;
	}
	free(tmp);
}

简化的非递归版本:
刚才那种思想是,一趟归并之后,我们一起拷贝回原数组,我们也可以一边归并一边拷贝,把拷贝放到循环里面,那就要改一下拷贝的下标为[0,end2],我们来看代码:

void MergeSortRonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
	}
	int gap = 1;
	while (gap < n)
	{
		int j = 0;
		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)//不需要拷贝
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)//将两个有序数组通过第三个数组合并一个有序的
			{
				if (a[begin1] <= a[begin2])//左区间的值小,放到tmp数组
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];//右区间的值小,放到tmp数组
				}
			}
			while (begin1 <= end1)//左区间没结束
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)//右区间没结束
			{
				tmp[j++] = a[begin2++];
			}
			for (int j =0 ; j <=end2; j++)//拷贝回原数组
			{
				a[j] = tmp[j];
			}
		}
		gap *= 2;
	}
	free(tmp);
}

相信大家看到这里对归并排序应该了解了,尤其是非递归版本,我们一定要画图,不然下标的控制很容易出错,希望大家可以下来自己去画画图

三、总结

今天关于归并排序的知识点我应该讲清楚了,再这里主流的一些排序我也几乎讲完了,接下来我还会将一个排序,也有实际应用,计数排序,是通过不比较数来进行排序的,方法也非常巧妙,欢迎大家来支持我,如果对于我这篇博客有什么不正确的或者建议都可以再评论区指正出来哦,今天的知识就先分享到这里了,我们下篇再见
在这里插入图片描述

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

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

相关文章

亮剑「驾舱」产品矩阵,百度要做智能化「卷王」

2023年&#xff0c;汽车智能化开启新一轮加速度。 伴随着汽车行业变革从“电动化”的上半场进入“智能化”的下半场&#xff0c;中国正成为智能驾驶技术领域的引领者和汽车智能化的核心战场。 据高工智能汽车研究院发布的《2023-2025年中国智能汽车产业链市场数据预测报告》预…

用机器学习sklearn+opencv-python过计算型验证码

目录 生成计算型验证码图片 用opencv-python处理图片 制作训练数据集 训练模型 识别验证码 总结与提高 源码下载 在本节我们将使用sklearn和opencv-python这两个库过掉计算型验证码&#xff0c;图片示例如下。 生成计算型验证码图片 要识别验证码&#xff0c;我们就需要…

【计算机图形学】裁剪算法(Cohen-Sutherland算法 中值分割算法 Liang-Barsky算法)

一 实验目的 编写直线段、多边形裁剪算法熟悉Cohen-Sutherland算法、中值分割算法和Liang-Barsky算法的裁剪二 实验算法理论分析Cohen-Sutherland算法&#xff1a; 中值分割算法&#xff1a; 与CS算法一样&#xff0c;首先对直线段端点进行编码&#xff0c;并把线段与窗口的关…

java创建线程的方法

线程是程序的一种操作单元&#xff0c;在程序中&#xff0c;一个线程和另一个线程是同时存在的。它是一个程序的一部分&#xff0c;但是他又是独立的&#xff0c;它不会影响到另一个线程的执行。但是多个线程同时运行时&#xff0c;会对系统资源造成一定的消耗。 线程之间的竞争…

[Linux] 基础IO

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【LINUX】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站 文…

SQL Server用户定义的函数(UDF)使用详解

SQL Server用户定义的函数一、背景知识1.1、用户定义函数的优点1.2、函数类型1.3、指引1.4、函数中的有效语句1.5、架构绑定函数1.6、指定参数二、创建用户定义函数2.1、限制和权限2.2、标量函数示例&#xff08;标量 UDF&#xff09;2.3、表值函数示例2.3.1、内联表值函数 &am…

leetcode26.删除数组中的重复项

1.原题目链接&#xff1a;力扣 2.题目&#xff1a; 3. 思路&#xff1a;使用两个指针:src与dst,刚开始均指向起始位置&#xff0c;如果src的值与dst值相同&#xff0c;src,如果src的值与dst的值不相同&#xff0c;dst,src的值赋值给dst,src,即两个指针比较&#xff0c;值不相同…

图像去模糊:MIMO-UNet 模型详解

本内容主要介绍实现图像去模糊的 MIMO-UNet 模型。 论文&#xff1a;Rethinking Coarse-to-Fine Approach in Single Image Deblurring 代码&#xff08;官方&#xff09;&#xff1a;https://github.com/chosj95/MIMO-UNet 1. 背景 由于深度学习的成功&#xff0c;基于卷…

docker搭建linux网络代理

docker搭建linux网络代理 1.准备 config.yaml 配置文件&#xff08;含订阅节点、规则&#xff0c;一般机场或者本地配置中含有&#xff09; 在root下创建文件夹命名为clash。上传配置好的config.yaml至clash文件夹。 2.配置 端口: port: 7890 ; socks-port: 7891 运行局域网…

Python网络爬虫之HTTP原理

写爬虫之前&#xff0c;我们还需要了解一些基础知识&#xff0c;如HTTP原理、网页的基础知识、爬虫的基本原理、Cookies的基本原理等。本文中&#xff0c;我们就对这些基础知识做一个简单的总结。 &#x1f31f;HTTP 基本原理 在本文中&#xff0c;我们会详细了解 HTTP的基本原…

医学图像分割之MedNeXt

论文&#xff1a;MedNeXt: Transformer-driven Scaling of ConvNets for Medical Image Segmentation ConvNeXt网络是一种借鉴Transformer的思想进行了改进实现的全卷积网络&#xff0c;其通过全卷积网络和逆向残差瓶颈单元的设计&#xff0c;可以实现比较大的空间感受野。本文…

【MySQL】聚合查询

目录 1、前言 2、插入查询结果 3、聚合查询 3.1 聚合函数 3.1.1 count 3.1.2 sum 3.1.3 avg 3.1.4 max 和 min 4、GROUP BY 子句 5、HAVING 关键字 1、前言 前面的内容已经把基础的增删改查介绍的差不多了&#xff0c;也介绍了表的相关约束&#xff0c; 从本期开始…

windows将exe或者bat封装成系统服务进行管理

NSSM介绍 NSSM(the Non-Sucking Service Manager)是Windows环境下一款免安装的服务管理软件&#xff0c;它可以将应用封装成服务&#xff0c;使之像windows服务可以设置自动启动等。并且可以监控程序运行状态&#xff0c;程序异常中断后自动启动&#xff0c;实现守护进程的功能…

和利时:自主可控 安全高效

4月13—15日&#xff0c;由易派客电子商务有限公司、中国石油和石油化工设备工业协会、北京长城电子商务有限公司共同主办的2023第二届易派客工业品展览会在苏州国际博览中心成功召开。本次展会以“绿色智造融通赋能”为主题&#xff0c;杭州和利时自动化有限公司&#xff08;简…

Cesium:Particle Systems粒子系统

官网文档,点击此处查看。 粒子系统简述 粒子系统是一种用于模拟复杂物理效果的图形学技术,它是一系列小图片的集合,当这些小图片被放在一起查看时,会形成一种更为模糊的对象,例如:火苗、烟、天气或者烟花。 粒子系统效果在电影和游中是十分普遍的。例如:飞机失…

Spark 之 解析json的复杂和嵌套数据结构

本文主要使用以下几种方法&#xff1a; 1&#xff0c;get_json_object()&#xff1a;从一个json 字符串中根据指定的json 路径抽取一个json 对象 2&#xff0c;from_json()&#xff1a;从一个json 字符串中按照指定的schema格式抽取出来作为DataFrame的列 3&#xff0c;to_j…

【洋桃一号板】STM32F103CBT6标准库函数驱动TM1640点亮数码管

一、今天介绍如何使用STM32F103CBT6驱动TM1640点亮数码管&#xff0c;硬件用的洋桃开发板&#xff0c;点亮后效果如下&#xff0c;六个数码管依次显示0.1.2.3.4.5.6.7 硬件原理图如下&#xff0c;只用到了单片机的两个IO口即可实现上图的效果&#xff0c;该开发板上用的是PA11…

chapter-3 -数据库数据模型

以下内容来源于MOOC学习—原课程请见&#xff1a;数据库原理与应用 考研复习 概述 关系及关系模式 笛卡尔积 定义在一组域上的有序对的集合&#xff0c; 域是一组具有相同类型的集合&#xff0c;比如自然数&#xff0c;长度小于n的字符串结合等【比如int age】 从n个域的每…

Linux工具make与makefile

Linux项目自动化构建工具-make/Makefile 目录Linux项目自动化构建工具-make/Makefile引言1、make && makefile2、make执行步骤2.1 依赖关系2.2 依赖方法3、项目清理4、伪目标 .PHONY5、文件的三个时间6、make的工作原理7、Linux下的第一个小程序认识缓冲区进度条①函数…

T5模型简单介绍

目录 一、概要 二、深入扩展 2.1 两个要素 2.2 预训练方法 一、概要 谷歌公司的研究人员提出的 T5&#xff08;Text-to-Text Transfer Transformer&#xff0c;有5个T开头的单词&#xff0c;所以叫做T5&#xff09;模型采用了一种与前述模型截然不同的策略&#xff1a;将不…