归并排序-MergeSort (C语言详解)

news2024/9/17 7:24:55

目录

  • 前言
  • 归并排序的思想
  • 归并排序的递归法
  • 归并排序的非递归法
  • 归并排序的时间复杂度与适用场景
  • 总结

前言

好久不见, 前面我们了解到了快速排序, 那么本篇旨在介绍另外一种排序, 它和快速排序的思想雷同, 但又有区别, 这就是归并排序, 如下图, 我们对比快速排序与归并排序.
在这里插入图片描述

本章也会深入介绍归并排序的两种写法, 递归版本的归并排序与非递归版本的归并排序.

博客主页:酷酷学!!!
您的支持是我更新的最大动力!


归并排序的思想

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

在这里插入图片描述
归并排序的步骤如下:

  1. 将待排序序列分为两个子序列,直到每个子序列只有一个元素为止。
  2. 比较两个子序列的首个元素,将较小的元素放入一个新的临时序列中。
  3. 如果其中一个子序列已经被遍历完,则将另一个子序列中剩余的元素依次放入临时序列中。
  4. 将临时序列中的元素复制回原始序列的相应位置,得到已经排序好的子序列。
  5. 重复步骤2-4,直到所有的子序列都已经归并完成。
  6. 返回最终排序好的序列。

在这里插入图片描述


归并排序的递归法

首先我们需要具备这样一个思想, 如果两组数据有序, 我们就可以进行归并, 取小的数据进行依次排列, 那么我们就需要一个临时的数组进行存放, 首先动态申请块与数组空间大小相同的空间, 然后进行内层函数的递归调用, 不能直接调用外层函数, 因为会重复申请空间.


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

	free(tmp);
	tmp = NULL;
}

接着, 我们来写内层函数, 首先需要给定一段区间, 用来进行此区间的排序, 我们进行递归的调用,直至区间中只有一个元素, 我们默认它为有序, 此时就可以进行归并排序, 这里与数组和链表将两个有序链表合成一个有序链表的思路是相通的, 这也可见学习算法是具有连贯性和螺旋式上升的一个过程, 接着我们需要拷贝临时数组的数据到原数组中, 每次归并一小段区间就要拷贝一次, 以免有不必要的麻烦, 到此我们的归并排序已经完成.是不是比较简单.

void _MergeSort(int* a, int* tmp, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int mid = (begin + end) / 2;
	_MergeSort(a, tmp, begin, mid);
	_MergeSort(a, tmp, mid + 1, end);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;

	int j = begin;
	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(a+begin, tmp+begin, sizeof(int) * (end - begin + 1));
}

归并排序的非递归法

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)
	{
		for (int i = 0; i < n; i += gap*2)
		{
			int j = i;
			int begin1 = i,end1 = i + gap -1;
			int begin2 = i + gap, end2 = i + gap * 2 - 1;

			if (begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			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(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

非递归法与递归法的思路是一模一样的,只不过呈现的形式有所不同, 首先我们来回忆一下递归法的过程, 就是先递归让数组的区间元素个数为1, 认为为有序数组, 然后依次回退进行归并, 我们这里也可以这样, 首先模拟一个元素为一个区间, 进行归并, 然后两个有序元素进行归并, 然后变成四个, 八个, 直到归并区间个数大于等于N.

第一步, 我们先分gap组, gap为每组归并的个数, 比如gap为1,那么每组就归并一个数据, 我们我们先看内层for循环, 进行第一次gap为1的归并, 此时一个数据与一个数据进行归并, 归并成含有两个数据的有序数组, 此时我们的目的就完成了, 这个思想与归并排序的思路是一致的, 此时有同学会问, 为什么i要+=gap*2, 因为每次归并一段区间, 比如第一次下标为0与下标为1进行归并, 第二次下标为2和下标为3进行归并, 所以每次要跳过两个组,然后进行下两组数据的归并.

在这里插入图片描述
i代表每组的起始位置,第一次归并完成之后, 我们进行第二次归并, gap为2, 进行22归并,然后44归并, 知道gap>=n, 但是这种也存在越界风险, 比如下图

在这里插入图片描述

我们对上面一组数据进行排序,发现十个数据, 进行排序, 除了begin1不会越界, 其他都有可能越界的风险,处理方法

在这里插入图片描述

对于begin2和end1越界, 那么我们就不需要归并了, 如果end2越界, 我们只需要重新调整一下区间, 代码如下:

			if (begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}

如此非递归版的归并排序我们也完成了.


归并排序的时间复杂度与适用场景

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

对于时间复杂度我们可以理解为递归深度, 就正如二叉树类似, 不难算出时间复杂度.

适用场景:

  1. 当需要对一个较大规模的数据集进行排序时,归并排序是一种比较高效的排序算法。它的时间复杂度相对较低,且具有稳定性,不会改变相同元素的相对次序。
  2. 归并排序适用于对链表进行排序。由于链表的特殊结构,使用归并排序在空间上更加节省,且对链表进行合并操作非常方便。
  3. 当数据集的存储方式不支持随机访问时,如外部排序,归并排序也是一个很好的选择。它可以对数据集进行分块读取,然后进行排序和合并。

总结

归并排序是一种经典的排序算法,它的基本思想是将待排序的序列分成两个子序列,分别进行递归地排序,然后将两个排好序的子序列合并成一个有序序列。

归并排序的具体步骤如下:

  1. 将待排序序列不断地划分,直到每个子序列只有一个元素。
  2. 对相邻的两个子序列进行合并,得到一个有序的子序列。
  3. 不断地合并子序列,直到最终得到一个有序的序列。

归并排序的时间复杂度为O(nlogn),其中n是待排序序列的长度。它的空间复杂度为O(n),因为在合并子序列的过程中需要额外的空间来存储临时结果。

归并排序是一种稳定的排序算法,它的优点是可以保持原序列中相同元素的相对顺序不变。它适用于对大规模数据进行排序,但由于需要额外的空间,所以对于内存有限的情况下可能不太适用。

总的来说,归并排序是一种简单而高效的排序算法,它的实现也相对容易理解。在实际应用中,可以根据具体情况选择是否使用归并排序来解决排序问题。



感谢关注, 如有疑问欢迎留言, 留言必回!!!

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

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

相关文章

在线JSON可视化工具--改进

先前发布了JSON格式化可视化在线工具&#xff0c;提供图形化界面显示结构关系功能&#xff0c;并提供JSON快速格式化、JSON压缩、快捷复制、下载导出、对存在语法错误的地方能明确显示&#xff0c;而且还支持全屏&#xff0c;极大扩大视野区域。 在线JSON格式化可视化工具 但…

[Labview] 二维数组写入表格

就一个二维数组写表&#xff0c;CSDN天天让我改进质量 简直是迫害完美主义 天知道Labview有什么思路好写&#xff0c;就一个破连连看(ˉ▽ˉ&#xff1b;)... 随便写点什么碎碎念占字数好了

Linux源码阅读笔记09-进程NICE案例分析1

task_nice task_nice函数功能&#xff1a;获取某个进程的nice值&#xff0c;其中nice值为进程的优先级&#xff0c;与静态优先级有关&#xff08;nicestatic_prio-120&#xff09;。 nice的取值范围&#xff1a;-20 ~ 19 内核源码 根据内核的注释可以知道&#xff1a;task_n…

时间12小时和24时转换方法

24小时时间转为12小时制 function convertTo12Hour(time24h){let [hours, minutes] time24h.split(:);let modifier 上午;if (parseInt(hours, 10) > 12) {modifier 下午;hours (parseInt(hours, 10) - 12).toString();}if (parseInt(hours, 10) 12) {modifier 下午;}…

Arduino 与树莓派常用的 IMU 传感器

惯性测量单元&#xff08;IMU&#xff09;是一种高度集成的传感器系统&#xff0c;广泛应用于需要高精度运动和姿态信息的领域。某些高精度要求下&#xff0c;还需要辅以温度、气压等其他传感器信息。 一、组成与功能 1. 组成 9 轴 IMU 由三个主要部分组成&#xff1a;3 轴加…

系留无人机+自组网+单兵图传:低空集群组网指挥系统技术详解

低空无人机集群的控制、调度、信息回传需要有高度可靠和稳定的无线通信链路来保障。我国发达的公网基础设施为上述应用创造了良好的条件&#xff0c;但低空应用必须要考虑到在极端情况下公网瘫痪造成的通信链路中断带来的影响&#xff0c;因此有必要在公网之外&#xff0c;寻求…

C语言----文件操作

1.为什么使用文件&#xff1f; 如果没有⽂件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运⾏程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数据进⾏持久化…

Vue85-Vuex的求和案例

一、需求 二、开发 2-1、index.js中vuex的代码 注意&#xff1a; 书写格式&#xff1a;actions中的函数名用小写&#xff01;mutations中的函数名&#xff0c;用大写。 注意&#xff1a; 2-2、组件count.vue中的代码 2-3、代码优化

oracle数据库无法open,报“ORA-01113 file 1 needs media recovery”错误,教你用“六脉神剑”来开库

作者介绍&#xff1a;老苏&#xff0c;10余年DBA工作运维经验&#xff0c;擅长Oracle、MySQL、PG数据库运维&#xff08;如安装迁移&#xff0c;性能优化、故障应急处理等&#xff09; 公众号&#xff1a;老苏畅谈运维 欢迎关注本人公众号&#xff0c;更多精彩与您分享。在系统…

Linux:文件系统与日志分析

一、block与inode 1.1、概述 文件是存储在硬盘上的&#xff0c;硬盘的最小存储单位叫做“扇区”(sector)&#xff0c;每个扇区存储512字节。 一般连续八个扇区组成一个"块”(block)&#xff0c;一个块是4K大小&#xff0c;是文件存取的最小单位。 文件数据包括实际数据…

如何在 Odoo 16 中通过函数创建和管理自定义字段

Odoo 几乎为每种功能提供了每种类型的字段。通常&#xff0c;我们为字段定义一个类定义并将其包含在模型中。但是&#xff0c;在某些业务实例中&#xff0c;我们可能需要通过添加新字段从用户界面本身修改模型。在本博客中&#xff0c;让我们研究如何定义自定义字段在视图中的位…

智能数字人系统的主要功能

智能数字人系统或虚拟数字人系统&#xff0c;是指利用人工智能技术构建的虚拟人物形象&#xff0c;能够与人进行自然交互的系统。数字人系统的主要功能包括以下几个方面。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1. 语言理解与…

从游戏到营销:抽卡机小程序的多维度应用探索

在数字化时代&#xff0c;小程序作为一种轻量级、即用即走的应用形态&#xff0c;正逐步渗透到人们生活的方方面面。其中&#xff0c;抽卡机小程序以其独特的趣味性和互动性&#xff0c;不仅在游戏领域大放异彩&#xff0c;更在营销领域展现出广阔的应用前景。本文将从游戏起源…

RocketMQ实战:一键在docker中搭建rocketmq和doshboard环境

在本篇博客中&#xff0c;我们将详细介绍如何在 Docker 环境中一键部署 RocketMQ 和其 Dashboard。这个过程基于一个预配置的 Docker Compose 文件&#xff0c;使得部署变得简单高效。 项目介绍 该项目提供了一套 Docker Compose 配置&#xff0c;用于快速部署 RocketMQ 及其…

【面向就业的Linux基础】从入门到熟练,探索Linux的秘密(七)-shell语法(5)

shell语法的一些知识和练习&#xff0c;可以当作笔记收藏一下&#xff01;&#xff01; 文章目录 前言 一、shell 二、shell语法 1.文件重定向 2.引入外部脚本 3.作业 总结 前言 shell语法的一些知识和练习&#xff0c;可以当作笔记收藏一下&#xff01;&#xff01; 提示&…

01:Linux的基本命令

Linux的基本命令 1、常识1.1、Linux的隐藏文件1.2、绝对路径与相对路径 2、基本命令2.1、ls2.2、cd2.3、pwd / mkdir / mv / touch / cp / rm / cat / rmdir2.4、ln2.5、man2.6、apt-get 本教程是使用的是Ubuntu14.04版本。 1、常识 1.1、Linux的隐藏文件 在Linux中&#xf…

全网首发-RocketMQ 4.0实现完美灰度发布方案

一、背景 为了控制发版带来的影响面等问题&#xff0c;我们公司基建团队自研灰度发布流程&#xff0c;目前几乎所有服务发版都会严格先走灰度发布验证再上线。当前已支持http、gRPC等接口调用方式进行灰度流量转发&#xff0c;使用消息队列进行业务实现的场景的暂不支持。 ps:…

优思学院|亚马逊如何因六西格玛而取得成功?

前言 上星期三&#xff0c;亚马逊&#xff08;Amazon&#xff09;市值首次超过2万亿美元&#xff0c;成为世界第五大巿值最高的企业&#xff0c;它是全球最大的互联网线上零售商之一。然而&#xff0c;或者你并不知道&#xff0c;亚马逊也是众多因六西格玛而取得成功的公司之一…

企业自身数据保护技巧你知道多少?用堡垒机可以实现吗?

随着企业数字化转型&#xff0c;越来越多的企业开始重视企业自身数据的安全&#xff0c;开始寻找保障数据安全的方法技巧。不少人在问&#xff0c;企业自身数据保护技巧有哪些&#xff1f;你知道吗&#xff1f;用堡垒机可以实现吗&#xff1f;今天我们来简单聊聊。 企业自身数据…

【udp报文】udp报文未自动分片,报文过长被拦截问题定位

问题现象 某局点出现一个奇怪的现象&#xff0c;客户端给服务端发送消息&#xff0c;服务端仅能收到小部分消息&#xff0c;大部分消息从客户端发出后&#xff0c;服务端都未收到。 问题定位 初步分析 根据现象初步分析&#xff0c;有可能是网络原因导致消息到服务端不可达&a…