<数据结构>NO11.归并排序|递归|非递归|优化

news2024/10/7 4:26:42

在这里插入图片描述

文章目录

  • 归并排序
    • 递归写法
    • 非递归写法
      • 修正方案1.归并一段拷贝一段
      • 修正方案2.修正区间
    • 算法优化
    • 算法分析
  • 归并排序的应用
    • 外排序和内排序

归并排序

递归写法

思路:
如果给出两个有序数组,我们很容易可以将它们合并为一个有序数组。因此当给出一个无序数组时,我们先将它们均分为两组有序数组,在将这两组有序数组合并为一个有序数组;而将原数组分成2组 有序数组的思路又是归并排序

递归的结束条件是什么?
当递归区间只有一个元素时结束递归

使用归并排序排3 5 7 6 9 2 10 8详细过程如下:

在这里插入图片描述

在这里插入图片描述

代码

void _MergeSort(int* arr, int* tmp, int begin, int end)
{
	//递归终止条件--区间只有一个数
	if (begin == end)
		return;
	//将数据均分未2组
	int mid = (begin + end) >> 1;
	int i = begin;
	//使两组均有序
	_MergeSort(arr, tmp, begin, mid);
	_MergeSort(arr, tmp, mid + 1, end);
	//有序数组归并---归并到tmp数组中
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
			tmp[i++] = arr[begin1++];
		else
			tmp[i++] = arr[begin2++];
	}
	while (begin1 <= end1)	tmp[i++] = arr[begin1++];
	while (begin2 <= end2)	tmp[i++] = arr[begin2++];
	//数据拷贝回元素组中
	memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
//归并排序
//时间复杂度O(nlogn)
//空间复杂度O(n+logn)
void MergeSort(int* arr, int sz)
{
	int* tmp = (int*)malloc(sizeof(int) * sz);//tmp作为临时数组存放有序数组合并后的数据
	_MergeSort(arr, tmp, 0, sz - 1);		  //因为要借助临时数组所以构造子函数
	free(tmp);
}

注意:

  1. 由于mid向上取整,所以不存在递归区间不存在的情况,结束条件只有区间只有一个值

  2. 每次拷贝时是从arr+begin处开始拷贝


非递归写法

归并排序的非递归写法不能和快速排序一样通过栈实现,如果使用栈存归并的区间,那么每次取出区间归并时栈顶元素出栈。这就导致了栈中不在存放有关该区间的任何信息,但是归并排序要求对区间的值进行保存,所以不能使用栈来实现

思路:
对数组依次进行一一归并、两两归并、四四归并,直至数组最终有序。

在这里插入图片描述

代码

//归并排序非递归
void MergeSortNonR(int* arr, int sz)
{
	int* tmp = (int*)malloc(sizeof(int) * sz);//临时数组用来存放分组排序的结果
	assert(tmp);
	int gap = 1;//从间隔为1开始归并
	while (gap < sz)
	{
		int j = 0;
		for (int i = 0; i < sz; i += 2 * gap)//以gap为间隔归并完一组后归并下一组
		{
			//有序数组归并---归并到tmp数组中
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = end1 + 1, end2 = begin2 + gap - 1;
            //归并区间
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] < arr[begin2])
					tmp[j++] = arr[begin1++];
				else
					tmp[j++] = arr[begin2++];
			}
			while (begin1 <= end1)	tmp[j++] = arr[begin1++];
			while (begin2 <= end2)	tmp[j++] = arr[begin2++];
		}
		//拷贝回元素组中
		memcpy(arr, tmp, sizeof(int) * sz);
		gap *= 2;
	}  

运行结果
在这里插入图片描述

看似没有问题,但是当我们在原数组基础上添加1个数据再排序时程序会崩溃在这里插入图片描述

我们走读代码看看哪里出了问题
在这里插入图片描述

gap=2,i = 8时,begin1 = 8, end1 = 9,此时在访问arr[end1]就会越界。同理,如果数组元素是10个,那么当gap=4,i = 8时,begin1 = 8, end1 = 11,此时访问arr[end1]同样会越界,所以只有当数组元素为2的幂次方时,上述排序才正确。因此我们需要对上述代码进行修改


修改:数组越界一共有3种情况,分别是end1越界,begin1越界,end2越界
在这里插入图片描述

修正方案1.归并一段拷贝一段

上面已经提过了当数据个数为9个时,两两归并时会访问arr[9], 并且将该值拷贝到不属于我们创建的堆空间中。造成了越界访问
为了避免将原数组外的空间拷贝我们选择归并一段拷贝一段,只拷贝我们当趟已经归并的区间.
如果end1或者begin2越界,直接break不归并这段区间也就拷贝,剩余的数据交给gap增大的下一次归并;如果遇见end2越界,则修正end2为sz-1,归并区间[begin1,end1]和[begin2, end2]
在这里插入图片描述

代码

//归并排序非递归(归并一趟拷贝一趟)
void MergeSortNonR(int* arr, int sz)
{
	int* tmp = (int*)malloc(sizeof(int) * sz);//临时数组用来存放分组排序的结果
	assert(tmp);
	int gap = 1;//从间隔为1开始归并
	while (gap < sz)
	{
		int j = 0;
		for (int i = 0; i < sz; i += 2 * gap)
		{
			//有序数组归并---归并到tmp数组中
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = end1 + 1, end2 = begin2 + gap - 1;
			//end1越界或者begin2越界跳出剩下为归并的数据交给下一次处理
			if (end1 >= sz || begin2 >= sz)
			{
				break;
			}
			//end2越界,修正end2
			if (begin2 < sz && end2 >= sz)
			{
				end2 = sz - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] < arr[begin2])
					tmp[j++] = arr[begin1++];
				else
					tmp[j++] = arr[begin2++];
			}
			while (begin1 <= end1)	tmp[j++] = arr[begin1++];
			while (begin2 <= end2)	tmp[j++] = arr[begin2++];
			//归并一段拷贝一段
			memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		//拷贝回元素组中
		//memcpy(arr, tmp, sizeof(int) * sz);
		gap *= 2;
	}  

修正方案2.修正区间

方案1是归并一段拷贝一段,越界的那一部分区间不进行处理,让下一趟归并处理上一趟违为归并的数据。
方案2是先将越界的那一部分区间进行修正(修正的区间可能合法也可能不合法),在对区间进行以gap为间隔的归并,每次归并完整个数组后再拷贝(方案1归并完一个区间就拷贝)。

代码

//归并排序非递归(修正区间)
void MergeSortNonR(int* arr, int sz)
{
	int* tmp = (int*)malloc(sizeof(int) * sz);//临时数组用来存放分组排序的结果
	assert(tmp);
	int gap = 1;//从间隔为1开始归并
	while (gap < sz)
	{
		int j = 0;
		for (int i = 0; i < sz; i += 2 * gap)
		{
			//有序数组归并---归并到tmp数组中
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = end1 + 1, end2 = begin2 + gap - 1;
			//end1越界
			if (end1 >= sz)
			{
				//end1修正为边界
				end1 = sz - 1;
				//[begin2, end2]修正为不存在的区间
				begin2 = sz;
				end2 = sz - 1;
			}
			else if (begin2 >= sz)
			{
				//修正为不存在的区间
				begin2 = sz;
				end2 = sz - 1;
			}
			else if (end2 >= sz)
			{
				end2 = sz - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] < arr[begin2])
					tmp[j++] = arr[begin1++];
				else
					tmp[j++] = arr[begin2++];
			}
			while (begin1 <= end1)	tmp[j++] = arr[begin1++];
			while (begin2 <= end2)	tmp[j++] = arr[begin2++];
			
		}
		//拷贝回元素组中
		memcpy(arr, tmp, sizeof(int) * sz);
		gap *= 2;
	}  
	
	free(tmp);
}

算法优化

假设我们排100000个数据,归并排序每次排序都会进行递归调用,每一个区间都会递归调用2次,因此当递归区间长度减小到某一个数时,该递归区间在进行递归调用时递归的次数就会非常多在这里插入图片描述

总共递归 2 h − 1 次,最后一层递归次数占了全部的 % 50 , 倒数第二层的递归调用占了全部的 % 25 , , 倒数第三层递归调用占了全部的 % 12.5 , 因此最后三层占了所有递归调用次数的 % 87.5 总共递归2^{h}-1次,最后一层递归次数占了全部的\%50,倒数第二层的递归调用占了全部的\%25,,倒数第三层递归调用占了全部的\%12.5,因此最后三层占了所有递归调用次数的\%87.5 总共递归2h1次,最后一层递归次数占了全部的%50,倒数第二层的递归调用占了全部的%25,,倒数第三层递归调用占了全部的%12.5,因此最后三层占了所有递归调用次数的%87.5

当递归区间元素个数为10时,还会接着递归3层,因此我们可以进行小区间优化当递归区间长度小于等于10时,直接进行插入排序,这样可以大大减少递归调用次数。

优化代码

void _MergeSort(int* arr, int* tmp, int begin, int end)
{
	//递归终止条件--区间只有一个数
	if (begin == end)
		return;
	//小区间优化
	if (end - begin + 1 <= 10)
	{
		InsertSort(arr + begin, end - begin + 1);
		return;
	}
	//将数据均分未2组
	int mid = (begin + end) >> 1;
	int i = begin;
	//使两组均有序
	_MergeSort(arr, tmp, begin, mid);
	_MergeSort(arr, tmp, mid + 1, end);
	//有序数组归并---归并到tmp数组中
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
			tmp[i++] = arr[begin1++];
		else
			tmp[i++] = arr[begin2++];
	}
	while (begin1 <= end1)	tmp[i++] = arr[begin1++];
	while (begin2 <= end2)	tmp[i++] = arr[begin2++];
	//拷贝回元素组中
	memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
//归并排序
void MergeSort(int* arr, int sz)
{
	int* tmp = (int*)malloc(sizeof(int) * sz);//tmp作为临时数组存放有序数组合并后的数据
	_MergeSort(arr, tmp, 0, sz - 1);		  //因为要借助临时数组所以构造子函数
	free(tmp);
}

优化结果
在这里插入图片描述


算法分析

归并排序时间复杂度空间复杂度
递归 O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n) O(n)
非递归 O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n) O(n)

归并排序的应用

外排序和内排序

对内存中的数据进行排序称为内排序,对外存储器上的数据进行排序称为外排序。
现代计算机的内存通常在 8 − 16 G B 8-16GB 816GB之间可以存储的整数在 21 亿 − 42 亿 21亿-42亿 21亿42亿之间,如果我们要排 200 亿 200亿 200亿的数据量怎么处理?

  1. 将数据放在文件中,将大文件分为n份小文件使每个小文件存储的数据量可以放在内存中排序
  2. 对将每个小文件的数据放在内存中进行排序(归并、快排),使得每个小文件有序
  3. 对n个小文件外使用归并排序进行外排序

注意:
外排序只能使用归并排序,因此文件数据不能通过下标访问随机读取,文件指针习惯顺序读写,而归并排序不需要下标访问

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

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

相关文章

如何解决循环引用的问题

本文已收录于专栏 《Java》 目录 概念说明发现问题解决问题分析问题具体解决注解说明代码实现效果展示 总结提升 概念说明 循环引用是指在对象之间存在相互引用的情况。具体来说&#xff0c;当一个对象A引用了另一个对象B&#xff0c;而对象B又引用了对象A&#xff0c;它们之间…

软件研发开发人员成本计算器

写了个简单的人员工资计算器&#xff0c;用最简单的人天数计算研发投入&#xff0c;其他费用计算稍后补充完善 软件研发成本计算 ——高级工程师中级工程师初级工程师平均日工资项目阶段高级工程师人天中级工程师人天初级工程师人天调研方案产品设计软件开发测试部署培训试运…

操作系统(王道)- 初识操作系统

一、什么是操作系统&#xff1f; 操作系统可以这么理解 操作系统的定义&#xff1a; 操作系统是整个计算机的硬件和软件的管理者&#xff01;&#xff01;&#xff01; 二、操作系统的功能和目标 作为计算机硬件和软件的管理者&#xff0c;操作系统做了什么&#xff1f; 操作系…

安达发|工业系统APS软件与MES软件有哪些区别?

MES 和 APS 有什么区别&#xff1f;MES 是一个制造执行系统&#xff0c;APS 是一个高级计划排程系统系统&#xff0c;两者是互补的关系&#xff0c;APS 和 MES 可以实现计划和车间执行的闭环管理模式。MES 和 APS 有什么区别&#xff1f; mes和aps的区别: MES 是智能化工厂…

【1++的Linux】之进程(一)

&#x1f44d;作者主页&#xff1a;进击的1 &#x1f929; 专栏链接&#xff1a;【1的Linux】 文章目录 一&#xff0c;冯诺依曼与操作系统概念1.1 冯诺依曼体系结构1.2 操作系统 二&#xff0c;进程的基本概念 一&#xff0c;冯诺依曼与操作系统概念 1.1 冯诺依曼体系结构 如…

如何关闭网页版【知乎】等页面的登录弹窗(以谷歌浏览器为例)

如何关闭网页版【知乎】等页面的登录弹窗&#xff08;以谷歌浏览器为例&#xff09; 在不登陆知乎的情况下仍然可以正常浏览页面&#xff0c;可是每次打开新页面会重复出现弹窗要求用户登录。如何屏蔽掉这一弹窗呢? 在浏览器中把知乎网址设置为禁止使用javascript 在chrome的…

C#盯盘小工具,“监”

也是一个小工具&#xff0c;用来看大A股票和主要指数行情的。 如果你是一个上班族&#xff0c;同时你也是一颗小韭菜&#xff0c;a股在开市交易盘中时刻惦记着股票是涨了还是跌了&#xff0c;却不能时刻盯着手机看行情&#xff0c;也不能在电脑上开着同花顺来回切窗口&#xff…

4.CSS(一)

目录 一、CSS简介 二、CSS基础选择器 &#xff08;一&#xff09;标签选择器 &#xff08;二&#xff09;类选择器 类选择器-多类名 &#xff08;三&#xff09;id选择器 &#xff08;四&#xff09;通配符选择器 &#xff08;五&#xff09;总结 三、CSS字体属性 &…

Acwing.906 区间分组(贪心)

题目 给定N个闭区间[ai,bi]&#xff0c;请你将这些区间分成若千组&#xff0c;使得每组内部的区间两两之间(包括端点)没有交集&#xff0c;并使得组数尽可能小。 输出最小组数。 输入格式 第一行包含整数N&#xff0c;表示区间数。 接下来N行&#xff0c;每行包含两个整数ai…

【算法基础】2.2 字典树/前缀树 Trie

文章目录 知识点cpp结构体模板 模板例题835. Trie字符串统计❤️❤️❤️❤️❤️143. 最大异或对&#x1f62d;&#x1f62d;&#x1f62d;&#x1f62d;&#x1f62d;&#xff08;Trie树的应用&#xff09; 相关题目练习208. 实现 Trie (前缀树)1804. 实现 Trie &#xff08;…

轮转数组——左旋数组,右旋数组

题目链接&#xff1a;力扣 左旋转字符串&#xff1a;【1234567】—左旋3下—>【4567123】 反转区间为前n的子串【3214567】反转区间为n到末尾的子串【3217654】反转整个字符串【4567123】 右旋转字符串&#xff1a;【1234567】—右旋3下—>【5671234】 反转整个字符…

一图看懂 pandas 模块(1):提供高性能、易用的数据结构和数据分析工具,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 pandas 模块&#xff1a;提供高性能、易用的数据结构和数据分析工具&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;摘要&#x1f9ca;模块图&#x…

SAP-MM-物料版次

业务背景介绍 UF公司其产成品会根据客户需求进行细节上零件变更,每次都需新增物料主数据以及搭建新的BOM和对应的工艺路线进行数据处理(即对客户A0001的产成品从A0010到B0010的变化,但物料描述还是原来的描述) 新增物料编号进行库存管控是较为合理的需求,但是因UF销售业务员…

在LLM的支持下使游戏NPC具有记忆化的方法

问题 使用GPT这样的LLM去处理游戏中的NPC和玩家的对话是个很好的点子&#xff0c;那么如何处理记忆化的问题呢。 因为LLM的输入tokens是有限制的&#xff0c;所以伴随着问题的记忆context是有窗口大小限制的&#xff0c;将所有的记忆输入LLM并不现实。 所以这里看到了stanfo…

Damiler EDI 项目 Excel 方案开源介绍

准备下载和运行 Daimler EDI 到 Excel 使用 Excel 生成一系列 EDI 文档与 Daimler 通信。 下载工作流 下载示例文件 Daimler EDI & Excel 方案简介 本文将继续分享Daimler示例工作流&#xff1a;使用Excel端口和Email端口生成一系列文件&#xff0c;完成与Daimler的…

解锁编程世界的魔法密码:探索算法的奥秘与应用

一个程序员一生中可能会邂逅各种各样的算法&#xff0c;但总有那么几种&#xff0c;是作为一个程序员一定会遇见且大概率需要掌握的算法。今天就来聊聊这些十分重要的“必抓&#xff01;”算法吧~* 一&#xff1a;引言 算法是解决问题和优化程序性能的核心&#xff0c;它是一…

Redis实战案例20-优化秒杀(一人一单)

查询优惠券判断秒杀库存&#xff1b;查询订单&#xff1b;校验一人一单&#xff1b;减库存、创建订单&#xff1b; 以上均为串行操作&#xff0c;执行效率不高&#xff0c;在高并发的场景下性能很一般 问题引出&#xff1a;如何在Redis中完成秒杀判断和校验一人一单的问题&…

【LeetCode热题100】打卡第39天:数组中第K个最大元素最大正方形

文章目录 【LeetCode热题100】打卡第39天&#xff1a;数组中第K个最大元素&最大正方形⛅前言 数组中的第K个最大元素&#x1f512;题目&#x1f511;题解 最大正方形&#x1f512;题目&#x1f511;题解 【LeetCode热题100】打卡第39天&#xff1a;数组中第K个最大元素&…

若依(Ruoyi)前后端分离版项目部署到服务器(Linux环境)后,刷新页面报错:404 Not Found

原文章:若依(ruoyi)前后端分离版使用教程之若依后端部署阿里云服务器步骤(超详细)_蓝多多的小仓库的博客-CSDN博客 问题: 在若依项目部署服务器后,可以正常运行,但如果执行刷新页面操作,便会出现404 Not Found。 原因: Nginx未正确配置。由于后台路由采用History模式…

软件测试银行项目面试过程

今天参加了一场比较正式的面试&#xff0c;汇丰银行的视频面试。在这里把面试的流程记录一下&#xff0c;结果还不确定&#xff0c;但是面试也是自我学习和成长的过程&#xff0c;所以记录下来大家也可以互相探讨一下。 请你做一下自我介绍&#xff1f;&#xff08;汇丰要求英…