【初阶数据结构】归并排序 - 分而治之的排序魔法

news2024/10/17 5:20:07

文章目录

  • 前言
  • 1. 什么是归并排序?
    • 1.1 归并排序的步骤
  • 2. 归并排序的代码实现
    • 2.1 归并排序代码的关键部分讲解
      • 2.1.1 利用递归
      • 2.1.2 将拆解的数组的元素放到一个临时空间中进行重新排序
      • 2.1.3 将在临时空间中排好的数组复制到目标数组中
  • 3. 归并排序的非递归写法

前言

本文讲解的排序算法是归并排序,作为归并算法,其有着快速排序算法没有的特性,也是面试比较常考的算法之一。本文会重点讲解思路以及代码的实现。

OK,让我们来一场酣畅淋漓的排序冒险吧!!!

哈哈哈

1. 什么是归并排序?

归并排序(MergeSort)是一种基于分治法的高效排序算法,具有稳定性和较好的时间复杂度。归并排序的基本思想是将待排序数组递归地分成两个子数组,分别对这两个子数组进行排序,然后再将它们合并成一个有序数组。

我给大家看一下归并排序的动图:
归并排序的动图

1.1 归并排序的步骤

  1. 🍉分解:将数组从中间分为两部分,递归地对子数组进行相同的操作,直到子数组的长度为1(即每个子数组只有一个元素,天然有序)。
  2. 🍉合并:将两个有序的子数组合并成一个有序数组。合并的过程通过比较两个子数组的元素大小,按序依次放入目标数组。
  3. 🍉重复上述步骤,直到所有子数组都合并成一个有序数组。

也许你看到上面的步骤有点云里雾里的感觉,没有关系,下面我将给出一个具体的例子,带着大家,去理解上面的步骤。

🍇例子:假设我们要对数组 [3, 1, 4, 1, 5, 9, 2, 6] 进行归并排序:

  1. 将数组不断分成两部分,直到每个部分只有一个元素:
    [3, 1, 4, 1, 5, 9, 2, 6]
    • 分成 [3, 1, 4, 1] 和 [5, 9, 2, 6]
    • 再分成 [3, 1] 和 [4, 1],以及 [5, 9] 和 [2, 6]
    • 继续分到每个部分只有一个元素:[3], [1], [4], [1], [5], [9], [2], [6]
  1. 合并有序子数组:
    • 将 [3] 和 [1] 合并为 [1, 3],将 [4] 和 [1] 合并为 [1, 4]
    • 将 [5] 和 [9] 合并为 [5, 9],将 [2] 和 [6] 合并为 [2, 6]
    • 将 [1, 3] 和 [1, 4] 合并为 [1, 1, 3, 4],将 [5, 9] 和 [2, 6] 合并为 [2, 5, 6, 9]
    • 最后将 [1, 1, 3, 4] 和 [2, 5, 6, 9] 合并为 [1, 1, 2, 3, 4, 5, 6, 9]
    • 最终得到有序数组 [1, 1, 2, 3, 4, 5, 6, 9]。

相信来看完上面的例子,你已经了解了归并排序的玩法了,那么接下来我们就得用代码来实现了。


2. 归并排序的代码实现

大家可以对照着这幅图来理解:

归并排序图片
可以看到归并排序的思想一定可以用递归来思想,接下来,我先给大家完整的代码,之后会对代码的关键部分讲解:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
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, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		if(a[begin1] >= a[begin2])
		{
			tmp[i++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	memcpy(a + left, tmp + left, sizeof(int)*(right-left+1));
	
}


//归并排序
void MergeSort(int* a, int n)
{
	int left = 0, right = n - 1;
	int* tmp = (int*)malloc(n * sizeof(int));

	if (tmp == NULL) 
	{
		perror("malloc fail");
		return;
	}

	_MergeSort(a,left,right,tmp);	
}

这里我们采用的是用_MergeSort这个函数写归并排序的核心代码,这种写法的结构也是现在比较推崇的。

2.1 归并排序代码的关键部分讲解

为了让大家更好的吸收上述代码的思想,这里我将会详细的讲解代码关键部分的逻辑。
归并排序图片

2.1.1 利用递归

递归
我们直到归并排序是采用分治思想的,其原理就是将一个数组拆解成一个一个有序的区间,最后经过比较合并之后才会称为一个有序的数组。那么,我们该如何,将数组拆成一个个区间呢?

利用递归就可以实现。有的人可能会问了,为什么会使用递归呢?

你可以这样想:我每次以数组中间位置的元素为界限,将数组拆分成两半。紧接着,又对已经拆分成两半的那两个数组再次进行这个操作。直至拆到没有元素或者是只剩一个元素为止,所以我们就可以推导出递归的条件为:左区间是否大于等于右区间。

2.1.2 将拆解的数组的元素放到一个临时空间中进行重新排序

图片
这段代码的作用:将拆解的数组的元素放到一个临时空间中进行重新排序。

有的人会问,那最后两个循环是怎么回事?

其实是这样的,你可以假设现在你有两个有序的数组,你要将这两个数组组成一个有序(升序)的数组,你会这么做:首先,你会分别拿出两个数组中的第一个元素互相比较,发现有其中一个元素的值比另一个要小,你就把那个小的元素的值放到一个你为一个有序(升序)的数组而申请的内存空间中,之后就拿下一个元素跟这个上次那个元素的值进行比较。
到后面,你会发现肯定有个数组里面的元素是没有被选中的,但是我们也不知道是哪一个,所以我们就可以都遍历一遍找到还没有被选中的元素,将它加进到你申请的空间中。

2.1.3 将在临时空间中排好的数组复制到目标数组中

这里你可以使用for循环,也可以用一个函数memcpy。这里我选择用后者。
函数
当然,里面的参数十分的讲究。

解释:

  • a+left:这里不能直接写a。原因是,每一次拷贝会目标数组的位置不都是数组的头。
  • tmp+left:这里也不能直接写为tmp。原因是,每一次让待排序数组位置的元素进行排序时,我们要求位置得一一对应。

hahaha


3. 归并排序的非递归写法

非递归归并排序的主要步骤:

  1. 🍉初始化步长(子数组长度)为 1,这意味着开始时每个元素作为一个单独的有序数组。
  2. 🍉两两合并相邻的子数组,将步长长度的相邻子数组进行合并,形成更大的有序子数组。
  3. 🍉逐步增加步长,每次将步长翻倍,然后继续合并相邻的子数组,直到整个数组完全排序。

非递归归并排序的核心思想:归并排序的递归版从上到下拆分数组,而非递归版则从下到上逐步合并,模拟递归中“合并”的过程。在这个过程中,我们通过循环控制子数组的长度,每次将相邻的子数组合并成更大的有序数组。

具体步骤:

  1. 🍇从长度为 1 的子数组开始,对整个数组进行遍历,每两个相邻的子数组合并成一个长度为 2 的有序子数组。
  2. 🍇再次遍历时,将步长翻倍至 2,对整个数组中相邻的两个长度为 2 的有序子数组进行合并,形成长度为 4 的有序子数组。
  3. 🍇重复此过程,不断将步长翻倍(4, 8, 16,直到步长大于数组长度),直到整个数组有序。

由此,我们就可以通过上述思想,写出两个版本的代码:
版本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 / 2)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			//划分区间:[begin1,end1][begin2,end2]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//控制区间的边界
			if (end1 >= n)
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

			int j = i;

			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));


		}

		memcpy(a, tmp, sizeof(int) * n);

		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

版本2:

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 / 2)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			//划分区间:[begin1,end1][begin2,end2]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			
			
			int j = i;

			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;
}

好了,到这里归并排序的内容就已经全部讲完了。如果大家觉得本文写的还不错的话。麻烦给偶点个赞吧!!!

哈哈哈

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

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

相关文章

未来汽车究竟该是什么样子?

24年10月14日&#xff0c;在中国&#xff08;深圳&#xff09;机器视觉展暨机器视觉技术及工业应用研讨会上&#xff0c;同行者分享了未来智能座舱应该长什么样子。 受此启发&#xff0c;个人觉得当前大多数新能源车都极力想做出电动感&#xff0c;但是布局传统没跳出来&#…

DAY52WEB 攻防-XSS 跨站反射型存储型DOM 型标签闭合输入输出JS 代码解析

#知识点&#xff1a; 1、XSS跨站-输入输出-原理&分类&闭合 2、XSS跨站-分类测试-反射&存储&DOM 演示案例&#xff1a; ➢XSS跨站-输入输出-原理&分类&闭合 ➢XSS跨站-分类测试-反射&存储&DOM #XSS跨站-输入输出-原理&分类&闭合 漏…

安防监控摄像头图传模组,1公里WiFi无线传输方案,监控新科技

在数字化浪潮汹涌的今天&#xff0c;安防监控领域也迎来了技术革新的春风。今天&#xff0c;我们就来聊聊这一领域的产品——摄像头图传模组&#xff0c;以及它如何借助飞睿智能1公里WiFi无线传输技术&#xff0c;为安防监控带来未有的便利与高效。 一、安防监控的新篇章 随着…

基于Java+Springboot+Vue开发的酒店客房预订管理系统

项目简介 该项目是基于JavaSpringbootVue开发的酒店客房预订管理系统&#xff08;前后端分离&#xff09;&#xff0c;这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能&#xff0c;同时锻炼他们的项目设计与开发能力。通过学习基于Java…

【AI学习】Mamba学习(七):HiPPO通用框架介绍

HiPPO这篇论文《HiPPO: Recurrent Memory with Optimal Polynomial Projections》&#xff0c;提出了一个通用框架。 我们再重新看一下论文的摘要&#xff1a; 从连续数据中学习的一个核心问题是&#xff0c;随着更多数据的处理&#xff0c;以增量方式表示累积历史。我们介绍了…

【记录】Windows|Windows 修改字体大全(Windows 桌面、VSCode、浏览器)

【记录】Windows&#xff5c;Windows 修改字体大全&#xff08;Windows 桌面、VSCode、浏览器&#xff09; 前言 最近从学长那里发现了一款非常美观的衡水体字体——Maple Mono SC NF。您可以通过以下链接下载该字体&#xff1a;https://github.com/subframe7536/maple-font/…

【C语言刷力扣】1832.判断句子是否为全字母句

题目&#xff1a; 法一 bool checkIfPangram(char* sentence) {int str[256];memset(str, 0, sizeof(int));for (int i 0; i < strlen(sentence); i) {str[ sentence[i] ];}for (int j a; j < z; j) {if (!str[j]) return false;}return true; } 法二 动态分配 typ…

C++ operator new和operator delete的深入讲解

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;C知识点的补充_Jason_from_China的博客-CSDN博客 前言 关于operator new和operator delete我们需要明确一个概念&#xff0c;这两个都是…

2024全新UI网址发布页源码带黑夜模式

源码简介 2024全新UI网址发布页源码带黑夜模式&#xff0c;带网站延迟测速&#xff0c;该源码是html源码&#xff0c;可以做个引导页面。 安装教程 纯HTML&#xff0c;直接将压缩包上传网站目录解压即可(或在本地直接打开) 首页截图 源码下载 2024全新UI网址发布页源码带黑…

​​Vision Pro/空间计算小白体系化练手案例:从入门到精通的实战项目指南

引言 随着苹果Vision Pro的发布,空间计算技术正迎来前所未有的发展机遇。对于希望进入这一领域的开发者来说,掌握visionOS及相关工具链是至关重要的第一步。本文将介绍一系列由浅入深的实战项目,旨在帮助初学者系统地学习并实践visionOS开发。这些项目不仅适合个人学习,也…

踩坑日记:线上接口超时问题排查

1.背景: 上线后,功能测试. 进入小程序页面发现很慢,耗时超过5秒,打开skywalking发现大量接口耗时都很高. 2.top命令 服务器top命令查看cpu资源发现占用并不高 3.mysql查看sql运行情况 # 当前运行的所有事务 select * from information_schema.innodb_trx; 1 | …

国内液压一体式潜孔钻机市场相对分散

根据研究团队调研统计&#xff0c;2023年全球液压一体式潜孔钻机市场销售额达到了52亿元&#xff0c;预计2030年将达到90亿元&#xff0c;年复合增长率&#xff08;CAGR&#xff09;为7.1%&#xff08;2024-2030&#xff09;。中国市场在过去几年变化较快&#xff0c;2023年市场…

计算机网络基础(1)

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络基础 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. 计算机网…

100. UE5 GAS RPG 显示范围魔法的攻击范围

在这一篇里&#xff0c;我们将制作一个范围魔法&#xff0c;释放魔法时&#xff0c;我们将在鼠标拾取位置绘制一个魔法光圈&#xff0c;用于显示技能释放时攻击的范围&#xff0c;然后再次点击可以释放技能。 创建贴花类 魔法范围标识的光圈&#xff0c;我们采用贴花实现&…

AI 代码工具大揭秘:提高编程效率的必备神器!

近年来&#xff0c;人工智能得到了迅猛的发展&#xff0c;并在各行各业都得到了广泛应用。尤其是近两年来&#xff0c;AI开发工具逐渐成为开发者们的新宠&#xff0c;其中GitHub Copilot更是引发了无限可能性的探索。在本文中&#xff0c;我将向大家介绍一些实用性较高的智能开…

徒步1 -经典贪心

# 问题描述 小明想从A徒步到B&#xff0c;总路程需要M天&#xff0c;路程中为了确保安全&#xff0c;小明每天需要消耗1份食物。 在起点及路程当中&#xff0c;零星分布着N个补给站&#xff0c;可以补充食物&#xff0c;不同补给站的食物价格可能不同。 请问小明若要安全完…

求双十一宠物空气净化器推荐!希喂、范罗士、有哈哪款好用?

不知道大家有没有觉得&#xff0c;今年的双十一比以往要冷清些。还是朋友提醒我才知道&#xff0c;预售已经开始几天了&#xff0c;真切的体会到了消费降级带来的变化。不过自己的东西可买可不买&#xff0c;猫主子的口粮和生活用品还是要保证的。 把小猫接回来已经一年了&…

HTTP服务

一.http协议的介绍 http应用层协议 超文本传输协议&#xff08;比如网站上面的超链接&#xff09; 作用&#xff1a;构建网站服务器&#xff0c;可以在客户端与网站服务器之间传输文本数据。 浏览器会将文本数据解析成对应的图片&#xff0c;视频进行展示。 1.网站类型 静…

字符串(4)_字符串相乘_高精度乘法

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 字符串(4)_字符串相乘_高精度乘法 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目…

AD9361 的 TX 输出中添加前置放大器,并在 RX 输入中添加 LNA。

AD9361 的 TX 输出中添加前置放大器&#xff0c;并在 RX 输入中添加 LNA。 https://www.analog.com/en/resources/evaluation-hardware-and-software/evaluation-boards-kits/AD-TRXBOOST1-EBZ.html https://wiki.analog.com/resources/eval/user-guides/ad-trxboost1-ebz/in…