【排序】归并排序

news2025/1/20 0:54:39

归并排序

  • 动图演示:

在这里插入图片描述

  • 基本思想:分治思想

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

假设我们有左右两块有序区间的数组,可以对它直接进行合并。此时我们需要借助第三方数组,一次比较两块区间的起始位置,把小的那个放到新数组,随后依次比较,小的就放新数组,一直到结束。

但是现在存在一个问题,上述条件是假设了左半区间和右半区间有序,但是原先数组是无序的,也就是左半区间和右半区间均无序。怎么才能达到左半区间和右半区间有序最后再归并成整体有序呢?这就体现到了分治的思想了,将数组一直分,分到1个1个的,归并成有序变成2个2个的,然后归并成有序成4个4个的,最后再4个4个的归并成有序,最终至整体有序。

  • 画图解析其完整的归并过程:

在这里插入图片描述

这里我们先用代码实现其分解递归的过程,并用打印法表示其结果:

在这里插入图片描述

画图演示其部分递归分治的过程:

在这里插入图片描述

  • 总代码如下:
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return; //区间不存在就返回
	int mid = (begin + end) / 2;
	//[begin, mid] [mid+1, end]
	_MergeSort(a, begin, mid, tmp); //递归左半
	_MergeSort(a, mid + 1, end, tmp); //递归右半
 
	//归并[begin, mid] [mid+1, end]
	//printf("归并[%d,%d][%d,%d]\n", begin, mid, mid + 1, end);
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int index = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		//将较小的值放到tmp数组里头
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}
	//如若begin2先走完,把begin1后面的元素拷贝到新数组
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
	//如若begin1先走完,把begin2后面的元素拷贝到新数组
	while (begin2 <= end2)
	{
		tmp[index++] = a[begin2++];
	}
	//归并结束后,把tmp数组拷贝到原数组
	memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
 
//归并排序
void MergeSort(int* a, int n)
{
	//malloc一块新数组
	int* tmp = (int*)malloc(sizeof(int) * n);
	assert(tmp);
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

归并排序非递归

  • 思想:

归并的非递归不需要借助栈,直接使用循环即可。递归版中我们是对数组进行划分成最小单位,这里非递归我们直接把它看成最小单位进行归并。我们可以通过控制间距gap来完成,先看图:

在这里插入图片描述

上述情况其实是在理想状态下可行的,只要数组长度不是2的次方倍都会出现问题,先简要看下理想状态下的伪代码,并用printf打印下归并过程:

在这里插入图片描述

再强调一遍,只要数组长度不是2的次方倍都会出现问题,像上述长度为8没有问题,那如若长度为6呢?

在这里插入图片描述

当长度为6不再是2的次方数时就运行出现问题了,综上我们需要考虑下极端情况:根据上述的区间范围,我们可以总结出以下三个可能会出现越界的情况:

  1. end1越界。
  2. begin2越界。
  3. end2越界。

1、end2越界:

在这里插入图片描述

2、begin2和end2均越界:

在这里插入图片描述

3、end1和begin2和end2均越界 :

在这里插入图片描述

综上,我们需要单独对这些极端情况处理。

//end1越界,修正即可
if (end1 >= n)
{
	end1 = n - 1;
}
//begin2越界,第二个区间不存在
if (begin2 >= n)
{
	begin2 = n;
	end2 = n - 1;
}
//begin2 ok,end2越界,修正下end2即可
if (begin2 < n && end2 >= n)
{
	end2 = n - 1;
}
  • 总代码如下:
//归并非递归
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	assert(tmp);
	int gap = 1;
	while (gap < n)
	{
		//分组归并,间距为gap是一组,两两归并
		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;
			//end1越界,修正即可
			if (end1 >= n)
			{
				end1 = n - 1;
			}
			//begin2越界,第二个区间不存在
			if (begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			//begin2 ok,end2越界,修正下end2即可
			if (begin2 < n && end2 >= n)
			{
				end2 = n - 1;
			}
			printf("归并[%d,%d][%d,%d]\n", begin1, end1, begin2, end2);
			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				//将较小的值放到tmp数组里头
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			//如若begin2先走完,把begin1后面的元素拷贝到新数组
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			//如若begin1先走完,把begin2后面的元素拷贝到新数组
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
		}
		memcpy(a, tmp, n * sizeof(int));
		gap *= 2;
	}
	free(tmp);
}

归并排序特性总结

1、归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

2、时间复杂度:O(N*logN)。

3、空间复杂度:O(N)。

4、稳定性:稳定 。

内排序和外排序

在排序中,分为内排序和外排序,简单了解下其概念:

  • 内排序:数据量较少,在内存中进行排序。
  • 外排序:数据量很大,在磁盘上进行排序。

而我们前面学习的排序中,归并排序既可作为内排序,也可作为外排序,而其它几个排序只能是内排序,这也就说明了在处理数据量很大时,采用归并排序才能解决,其它排序不可。

如若我要排10亿个整数,就只能使用归并排序了,现在来简要算下其占比大小:

  • 1G = 1024MB
  • 1MB = 1024KB
  • 1KB = 1024Byte
  • 综上1G = 102410241024Byte,而10亿个整数40亿Byte,所以10亿个整数占4G

现在有10亿个整数(4G)的文件,只给你1G的运行内存,请对文件中的10亿个数进行排序。

核心思想: 数据量大,加载不到内存。想办法控制两个有序文件,两个有序文件归并成一个更大的有序文件。可以把这4G的文件分成4等份,每一份1G,分别读到内存进行归并排序,排完后再写回到磁盘小文件。

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

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

相关文章

CSS 评分器星星效果

<template><view class="rating"><!-- 5颗星 --><input value="5" name="rating" id="star5" type="radio"><label for="star5"></label><!-- 4颗星 --><input val…

中小学信息学奥赛CSP-J认证 CCF非专业级别软件能力认证-入门组初赛模拟题第一套(完善程序题)

CCF认证CSP-J入门组模拟测试题第一套 三、完善程序题 第一题 九宫格 请完善下面的程序,将1~9个数字分别填人3x3的九宫格中,第一行的三个数字组成一个三位数。要使第二行的三位数是第一行的2倍,第三行的三位数是第一行的3倍且每个格子里的数字都不能重复,现在要求输出所有的填…

08:K8S资源对象管理|服务与负载均衡|Ingress

K8S资源对象管理&#xff5c;服务与负载均衡&#xff5c;Ingress DaemonSet控制器污点策略容忍容忍污点 其他资源对象Job资源对象 有限生命周期CronJob资源对象 集群服务服务自动发现headless服务 实现服务定位与查找 服务类型 Ingress插件 发布服务的方式 DaemonSet控制器 Da…

GEE:梯度提升树(Gradient Boosting Tree)回归教程(样本点、特征添加、训练、精度、参数优化)

作者:CSDN @ _养乐多_ 对于分类问题,这个输出通常是一个类别标签 ,而对于回归问题,输出通常是一个连续的数值。回归可以应用于多种场景,包括预测土壤PH值、土壤有机碳、土壤水分、碳密度、生物量、气温、海冰厚度、不透水面积百分比、植被覆盖度等。 本文将介绍在Google…

vue-进阶语法(四)

目录 v-model原理 v-model应用于组件 sync修饰符 ref 和 $refs&#xff08;重点&#xff09; $nextTick v-model原理 原理&#xff1a;v-model本质上是一个语法糖。例如应用在输入框上&#xff0c;就是 value属性 和 input事件 的合写。 作用&#xff1a;提供数据的双向…

【开源】JAVA+Vue.js实现天然气工程运维系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统角色分类2.2 核心功能2.2.1 流程 12.2.2 流程 22.3 各角色功能2.3.1 系统管理员功能2.3.2 用户服务部功能2.3.3 分公司&#xff08;施工单位&#xff09;功能2.3.3.1 技术员角色功能2.3.3.2 材料员角色功能 2.3.4 安…

ClickHouse--03--数据类型

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 数据类型1. Int2.FloattoFloat32(...) 用来将字符串转换成 Float32 类型的函数toFloat64(...) 用来将字符串转换成 Float64 类型的函数 3.DecimaltoDecimal32(value…

云原生介绍与容器的基本概念

云原生介绍 1、云原生的定义 云原生为用户指定了一条低心智负担的、敏捷的、能够以可扩展、可复制的方式最大化地利用云的能力、发挥云的价值的最佳路径。 2、云原生思想两个理论 第一个理论基础是&#xff1a;不可变基础设施。 第二个理论基础是&#xff1a;云应用编排理…

基于python深度学习的中文情感分析的系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Vue3高频知识点和写法

一 Vue插件 二 vue3项目创建 创建完成后npm install npm run dev 三 setup 一 响应式数据 setup函数是用来代替data和methods的写法的&#xff0c;在setup函数中声明的数据和函数&#xff0c;导出后可以在页面中使用。 但是暂时不是响应式数据&#xff0c;如果要响应式数据的…

备战蓝桥杯---动态规划(入门2)

今天主要介绍区间dp比较难的题&#xff1a; 下面是分析&#xff1a; 我们如果先固定点V0&#xff0c;那我们得去枚举两个点使它构成三角形&#xff0c;同时求目标值也比较难确定&#xff08;起始与终止都带0&#xff09;&#xff0c;于是我们考虑固定边&#xff0c;我们固定v0…

【开源】SpringBoot框架开发考研专业课程管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 考研高校模块2.3 高校教师管理模块2.4 考研专业模块2.5 考研政策模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 考研高校表3.2.2 高校教师表3.2.3 考研专业表3.2.4 考研政策表 四、系统展示五、核…

云原生容器化-4 Docker仓库

1.Docker仓库 1.1 Docker Hub docker仓库用于存放docker镜像&#xff0c;可以分为公用和私有两种。Docker Hub是全球公用的仓库&#xff0c;因服务器在国外&#xff0c;国内基本不可以&#xff1b;一般需要配置阿里、腾讯等加速器。公司内部而言&#xff0c;可以搭建私有的Do…

智能家居中可自行收集能量的无电池的无线设备

此图片来源于网络 1、背景 ZigBee是一种基于IEEE 802.15.4标准的低速短距离无线通信技术&#xff0c;用于创建个人区域网络。其名称来源于蜜蜂的八字舞&#xff0c;因为蜜蜂通过这种舞蹈来与同伴传递花粉的所在方位信息&#xff0c;从而构成了群体中的通信网络。ZigBee技术具…

【Linux】Kali Linux 系统安装详细教程(虚拟机)

目录 1.1 Kali linux简介 1.2 Kali Linux工具 1.3 VMware workstation和ESXi的区别 二、安装步骤 一、Kali概述 1.1 Kali linux简介 Kali Linux是基于Debian的Linux发行版&#xff0c; 设计用于数字取证操作系统。每一季度更新一次。由Offensive Security Ltd维护和资助。最…

【Tauri】(3):使用Tauri1.5版本,进行桌面应用开发,在windows上搭建环境,安装node,rust环境,可以打包成功,使用vite创建应用

1&#xff0c;视频地址&#xff1a; https://www.bilibili.com/video/BV1Ny421a7nA/ 【Tauri】&#xff08;3&#xff09;&#xff1a;使用Tauri1.5版本&#xff0c;进行桌面应用开发&#xff0c;在windows上搭建环境&#xff0c;安装node&#xff0c;rust环境&#xff0c;可以…

Linux第47步_安装支持linux的第三方库和mkimage工具

安装支持linux的第三方库和mkimage工具&#xff0c;做好移植前的准备工作。 编译linux内核之前&#xff0c;需要先在 ubuntu上安装“lzop库”和“libssl-dev库”&#xff0c;否则内核编译会失败。 mkimage工具会在zImage镜像文件的前面添加0x40个字节的头部信息,就可以得到uI…

利用Cloudflare Workers实现网页状态监控

首先 Fork cf-workers-status-page 浏览器地址栏输入 https://deploy.workers.cloudflare.com/?urlhttps://github.com/$YourUserName/cf-workers-status-page 获取 Cloudflare 账户内的 Account ID 和 API Token 授权的 token 需要 workes 的编辑权限 在 Github actio…

【C语言】动态内存深入了解(一口气刨根问底学完系列,全乎,建议三连点赞收藏)

目录 1.动态内存分配的原因 2.动态内存函数的介绍 2.1malloc和free函数 2.2calloc函数 2.3realloc函数 3. 常见的动态内存错误 3.1 对NULL指针的解引用操作 3.2 对动态开辟空间的越界访问 3.3 对非动态开辟内存使用free释放 3.4 使用free释放一块动态开辟内存的一部…

程序员与电脑:不眠之夜的背后故事

在这个数字化飞速发展的时代&#xff0c;程序员和他们的电脑成了不可分割的伙伴。 如果你有机会深夜走过城市的某个角落&#xff0c;透过窗户瞥见那些亮着的电脑屏幕&#xff0c;你可能会好奇&#xff1a;这些电脑为什么总是开着的&#xff1f; 难道程序员们都有失眠症吗&…