【数据结构】排序算法系列——归并排序(附源码+图解)

news2024/11/16 21:22:04

归并排序

归并排序从字面上来看,它的大致核心应与归并有关——归并拆分开来,变成归类和合并,归类则是将数组进行有序化,合并则是将两个有序的数组进行合并变成一个有序的数组。

它的特点在于并不是一开始就将整个数组进行归类和调整,而是以一定的间隔数分成多次小的排序,最后再逐渐将小的排序的范围变大,最后变大到整个数组时,已经完全有序。

算法思想和图解

递归法(Top-down)

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤3直到某一指针到达序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列尾

迭代法(Bottom-up)

原理如下(假设序列共有n个元素)

  1. 将序列每相邻两个数字进行归并操作,形成**Ceil(n/2)**个序列,排序后每个序列包含两/一个元素
  2. 若此时序列数不是1个则将上述序列再次归并,形成**Ceil(n/4)**个序列,每个序列包含四/三个元素
  3. 重复步骤2,直到所有元素排序完毕,即序列数为1

界定比较的数据个数:一般按照2的倍数增长:两个互相比较、四个互相比较、八个互相比较…下图可以很好地说明这种方法

请添加图片描述

上图根据颜色的不同进行分组,可以看到先分成两个数据。再分成四个…

C语言代码分析

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	int mid = (begin + end) / 2;//中间值
	//(提问:如果不是2的倍数会不会有错——不会,归并本身和元素个数无关,基于"/"的特性)
	if (begin >= end)
	{
		return;
	}
	_MergeSort(a, begin, mid, tmp);//先从左边开始
	_MergeSort(a, mid + 1, end, tmp);//再从右边开始

	int begin1 = begin, end1 = end;//左边的起始位置和结束位置
	int begin2 = mid + 1, end2 = end;//右边的起始位置和结束位置
	int i = begin;//这里不能给0,因为递归时会多次调用
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	while (begin1 <= end1)//如果左边还有剩余
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)//如果右边还有剩余
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(tmp + begin, a + begin, sizeof(int) * (end - begin + 1));//将排好序的拷贝回去
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		return;
	}
	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
}

非递归的归并排序

//非递归归并
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	//归并排序为什么不能用栈?——因为递归的时候需要保存现场,栈不方便
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		return;
	}
	//对于非递归,我们可以两两进行排序,然后拷贝回去再进行四四排序,直到全部排序
	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 (end1 >= n)
			//{
			//	end1 = n - 1;
			//	begin2 = n;//这里不能给n-1,因为下面会++,会越界
			//	end2 = n - 1;
			//}
			//else if (begin2 >= n)
			//{
			//	begin2 = n;
			//	end2 = n - 1;
			//}
			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

			int j = i;//这里不能给0,因为递归时会多次调用
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)//如果左边还有剩余
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)//如果右边还有剩余
			{
				tmp[j++] = a[begin2++];
			}
			//拷贝
			memcpy(tmp + i, a + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
	free(tmp);
}

时间复杂度

O(nlogn)

稳定性

鉴于归并排序会改变前后元素的相对位置,所以:不稳定

分治思想

我们发现快速排序和归并排序都使用了一种分治的思想,这里对其进行简单介绍一下,以便更好地理解归并排序

在这里插入图片描述

在这里插入图片描述

分治模式在每层递归时都有三个步骤:
1.分解原问题为若干子问题,这些子问题是原问题的规模较小的实例,也就是说实际上子问题也可以看作是一个原问题。
2.解决这些子问题,递归地求解各子问题。然而,若子问题的规模足够小,则直接求解。
3.合并这些子问题的解成原问题的解。
归并排序算法完全遵循分治模式。直观上其操作如下:

  1. 分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列。
  2. 解决:使用归并排序递归地排序两个子序列。
  3. 合并:合并两个已排序的子序列以产生已排序的答案。

所以从这个角度来看实际上分治思想也是基于递归的思想来解决问题的。所以间接地也能帮助我们理解递归的思想。

在这里插入图片描述

在计算机科学中,分治法(英语:Divide and conquer)是建基于多项分支递归的一种很重要的算法范型。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
在这里插入图片描述

分治法能解决的问题一般有如下特征:

  • 该问题的规模缩小到一定的程度就可以容易地解决。
  • 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质,利用该问题分解出的子问题的解可以合并为该问题的解。
  • 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。

总的来说,分治法也可以被称作一种算法,它是一种基于递归的、“分而治之”的算法思想。

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

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

相关文章

MODBUS TCP 转 CANOpen

产品概述 SG-TCP-COE-210 网关可以实现将 CANOpen 接口设备连接到 MODBUS TCP 网络中。用户不需要了解具体的 CANOpen 和 Modbus TCP 协议即可实现将CANOpen 设备挂载到 MODBUS TCP 接口的 PLC 上&#xff0c;并和 CANOpen 设备进行数据交互。 产品特点 &#xf…

Qt 构建目录

Qt Creator新建项目时&#xff0c;选择构建套件是必要的一环&#xff1a; 构建目录的默认设置 在Qt Creator中&#xff0c;项目的构建目录通常是默认设置的&#xff0c;位于项目文件夹内的一个子文件夹中&#xff0c;如&#xff1a;build-项目名-Desktop_Qt_版本号_编译器类型_…

【Linux-基础IO】文件描述符重定向原理缓冲区

文件描述符 文件描述符的概念和原理 通过上述内容&#xff0c;我们知道使用 open 系统调用打开文件时&#xff0c;系统会返回一个文件描述符。这个描述符用于后续的文件操作。 在C语言中默认会打开三个输入输出流&#xff0c;分别是stdin&#xff0c;stdout&#xff0c;stde…

JSP(Java Server Pages)基础使用二

简单练习在jsp页面上输出出乘法口诀表 既然大家都是来看这种代码的人了&#xff0c;那么这种输出乘法口诀表的这种简单算法肯定是难不住大家了&#xff0c;所以这次主要是来说jsp的使用格式问题。 <%--Created by IntelliJ IDEA.User: ***Date: 2024/7/18Time: 11:26To ch…

Web端云剪辑解决方案,提供多轨视频、音频、特效、字幕轨道可视化编辑

传统视频剪辑软件的繁琐安装、高昂硬件要求以及跨平台协作的局限性&#xff0c;让无数创意者望而却步。美摄科技作为云端视频编辑技术的领航者&#xff0c;携其革命性的Web端云剪辑解决方案&#xff0c;正重新定义视频创作的边界&#xff0c;让专业级视频剪辑触手可及&#xff…

LeetCode 909. 蛇梯棋

LeetCode 909. 蛇梯棋 给你一个大小为 n x n 的整数矩阵 board &#xff0c;方格按从 1 到 n2 编号&#xff0c;编号遵循 转行交替方式 &#xff0c;从左下角开始 &#xff08;即&#xff0c;从 board[n - 1][0] 开始&#xff09;的每一行改变方向。 你一开始位于棋盘上的方格 …

微服务(一)

目录 一、概念 1、单体架构 2、微服务 3、springcloud 二、微服务的拆分 1、微服务的拆分原则 1.1 什么时候拆 1.2 怎么拆 2、服务调用 2.1 resttemplate 2.2 远程调用 一、概念 1、单体架构 单体架构&#xff08;monolithic structure&#xff09;&#xff1a;顾名…

项目启动卡住不动Property ‘mapperLocations‘ was not specified.

问题如上图所示&#xff1b; 原因&#xff1a;在mapper打了个断点&#xff01;

js实现多行文本控件textarea,根据文本内容自适应窗口全部显示

概述 本人在使用html控件textarea&#xff0c;多行显示的时候&#xff0c;希望根据后台实际的文本&#xff0c;来全部显示文本内容&#xff0c;而不用再去操作滚动条查看全部文本。 本功能实现的难点在于&#xff0c;计算当前文本显示有多少行。 软件环境 编辑器&#xff1a…

8.11Zero Crossing Detection (零交叉检测)

基本概念 零交叉检测是一种基于二阶导数的边缘检测方法&#xff0c;它通过查找二阶导数过零点来定位边缘。 注意: OpenCV没有直接提供这种检测方法&#xff0c;但可以通过结合其他函数来实现。 在OpenCV中&#xff0c;基于C的Zero Crossing Detection&#xff08;零交叉检测&…

关于PHP方面需要掌握的一些基础语法

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于【PHP的基础语法】相关内容&#xff01;…

Unity开发绘画板——03.简单的实现绘制功能

从本篇文章开始&#xff0c;将带着大家一起写代码&#xff0c;我不会直接贴出成品代码&#xff0c;而是会把写代码的历程以及遇到的问题、如何解决这些问题都记录在文章里面&#xff0c;当然&#xff0c;同一个问题的解决方案可能会有很多&#xff0c;甚至有更好更高效的方式是…

零售业的数字化转型与消费者体验升级

在数字化浪潮的推动下&#xff0c;零售业正经历着前所未有的变革。数字化转型不仅为零售商带来了新的商业模式和运营效率的提升&#xff0c;更重要的是&#xff0c;它极大地提升了消费者的购物体验。金智维将探讨零售业如何通过数字化转型&#xff0c;实现线上线下融合、智能推…

【架构】NewSQL

文章目录 NewSQLTiDBTiDB 主要组件特点使用场景安装与部署 推荐阅读 NewSQL NewSQL是一种数据库管理系统(DBMS)的类别&#xff0c;它结合了NoSQL数据库的可扩展性和传统SQL数据库的事务一致性。具体来说&#xff0c;NewSQL数据库旨在解决传统关系型数据库在处理大规模并发事务…

通过pyenv local 3.6.1 这里设置了当前目录的python版本,通过pycharm基于这个版本创建一个虚拟环境

要在 PyCharm 中基于你通过 pyenv local 设置的 Python 版本创建虚拟环境&#xff0c;可以按照以下步骤进行操作&#xff1a; 步骤 1: 获取当前使用的 Python 路径 通过 pyenv 查找当前项目下的 Python 解释器路径&#xff0c;使用以下命令&#xff1a; pyenv which python …

Thread , ThreadLocal , ThreadLocalMap , Entry 之间的关系?

Thread , ThreadLocal , ThreadLocalMap , Entry 之间的关系&#xff1f; 首先ThradLocal是线程的本地副本&#xff0c;怎么理解这句话呢&#xff1f;一个Thread都有一个它自己的ThreadLocalMap。ThreadLocalMap不是HashMap的结构&#xff0c;而是一个Entry数组&#xff0c;里面…

报错解决方案

大模型-报错解决方案 百度千帆大模型 仅个人笔记使用&#xff0c;感谢点赞关注 百度千帆大模型 未开通付费模型 qianfan.errors.APIError: api return error, req_id: code: 17, msg: Open api daily request limit reached 可能的原因: 未开通所调用服务的付费权限&#xff0…

【设计模式-观察者模式】

定义 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;用于定义一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象&#xff08;被观察者&#xff09;的状态变化。当主题状态发生变化时&#xff0c;所有依赖于它的观察…

00DSP学习-F28379D学习准备(了解一个工程的构成)

叠甲 我也算初学F28379D&#xff0c;不对之处请大家斧正。不同型号的DSP在外设配置的函数上有一些区别&#xff0c;但是掌握一种对其他型号的来说则难度不大。对于我们而言学习DSP最终还是要用于算法验证&#xff0c;而DSP资源的最大化利用、代码效率提升等则是后话。 软件准…

大数据-146 Apache Kudu 安装运行 Dockerfile 模拟集群 启动测试

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…