十大排序 —— 归并排序

十大排序 —— 归并排序

  • 归并排序
  • 治(排序)
  • 归并排序的性能
  • 一些小总结

我们今天继续来学习排序算法 —— 归并排序:

归并排序

归并排序(Merge Sort)是一种高效的、稳定的排序算法,它采用分治法(Divide and Conquer)的思想来实现。归并排序的基本工作原理如下:

  1. (Divide):将当前序列分成两个尽可能相等的子序列。如果当前序列只有一个元素或者为空,则不需要进一步划分,因为它本身就是有序的。
  1. (Conquer):递归地对两个子序列分别进行归并排序。这意味着每个子序列都要重复执行划分和递归排序的过程,直到子序列不能再分割为止(即子序列中只有一个元素)。
  1. (Combine):将两个已经排序好的子序列合并成一个有序序列。合并的过程是通过比较两个子序列中的元素,将较小的元素先放入一个新的序列中,直到一个子序列为空,然后再将另一个子序列中剩余的元素依次加入新序列中。这个过程保证了新序列的整体有序性。

归并排序的关键操作是合并步骤,这也是算法名称的由来。整个算法通过这种方式逐步构建出整个序列的排序结果,从最小的子序列开始,一步步合并出更大范围的有序序列,直到整个序列变得有序。

我们一步一步来看:

归并排序首先要,将区间分成尽量相等的区间:
在这里插入图片描述

这里我们注意一下,我们一来对区间进行详尽的划分,中间并没有进行任何的其他操作。

我们可以用递归来一直模拟二分:

void merge_sort(std::vector<int>& array,int left,int right,std::vector<int>& temp) //temp是辅助数组{if (left >= right)return;//分int mid = left + (right - left) / 2;merge_sort(array, left, mid, temp);merge_sort(array, mid + 1, right, temp);}

划分到不能再划分时,我们进行第二步:

治(排序)

我们划分到了最后,会有两个部分的数据:
在这里插入图片描述

这个时候,我们要对两个部分数据进行排序,我们一边拿一进行比较:
在这里插入图片描述

我们用一个临时数组,用来存放比较之后的数据:
在这里插入图片描述在这里插入图片描述

此时begin2越界,只需要将左边的数据放入temp:
在这里插入图片描述

然后我们要放回原来的数组:
在这里插入图片描述整体是这样的:

在这里插入图片描述
在这里插入图片描述

// 归并排序函数
void merge_sort(std::vector<int>& array, int left, int right, std::vector<int>& temp) {// 基准情况:如果左边索引大于等于右边索引,说明区间内只有一个元素或无元素,不需要排序if (left >= right) {return;}// 分:找到中间索引,将当前区间分为两个子区间,递归地对它们进行排序int mid = left + (right - left) / 2; // 防止大数相加导致溢出merge_sort(array, left, mid, temp);   // 对左半区间排序merge_sort(array, mid + 1, right, temp); // 对右半区间排序// 在合并之前清空temp,确保在一个干净的环境中进行合并操作temp.clear();// 合:将两个有序子区间合并成一个有序区间int begin1 = left;  // 左子区间的起始索引int end1 = mid;     // 左子区间的结束索引int begin2 = mid + 1; // 右子区间的起始索引int end2 = right;   // 右子区间的结束索引// 合并两个子区间while (begin1 <= end1 && begin2 <= end2) {// 如果左子区间当前元素大于右子区间当前元素,将右子区间元素加入tempif (array[begin1] > array[begin2]) {temp.push_back(array[begin2++]);}// 反之,将左子区间元素加入tempelse {temp.push_back(array[begin1++]);}}// 处理剩余的元素,如果左半边还有剩余,全部加入tempwhile (begin1 <= end1) {temp.push_back(array[begin1++]);}// 如果右半边还有剩余,全部加入tempwhile (begin2 <= end2) {temp.push_back(array[begin2++]);}}

这里的合就是拷贝回原数组的过程:
在这里插入图片描述

    // 拷贝回原数组// 注意:这里使用i - left是因为temp是从0开始的,而原数组的子区间是从left开始的for (int i = left; i <= right; i++) {array[i] = temp[i - left];}
// 归并排序函数
void merge_sort(std::vector<int>& array, int left, int right, std::vector<int>& temp) {// 基准情况:如果左边索引大于等于右边索引,说明区间内只有一个元素或无元素,不需要排序if (left >= right) {return;}// 分:找到中间索引,将当前区间分为两个子区间,递归地对它们进行排序int mid = left + (right - left) / 2; // 防止大数相加导致溢出merge_sort(array, left, mid, temp);   // 对左半区间排序merge_sort(array, mid + 1, right, temp); // 对右半区间排序// 在合并之前清空temp,确保在一个干净的环境中进行合并操作temp.clear();// 合:将两个有序子区间合并成一个有序区间int begin1 = left;  // 左子区间的起始索引int end1 = mid;     // 左子区间的结束索引int begin2 = mid + 1; // 右子区间的起始索引int end2 = right;   // 右子区间的结束索引// 合并两个子区间while (begin1 <= end1 && begin2 <= end2) {// 如果左子区间当前元素大于右子区间当前元素,将右子区间元素加入tempif (array[begin1] > array[begin2]) {temp.push_back(array[begin2++]);}// 反之,将左子区间元素加入tempelse {temp.push_back(array[begin1++]);}}// 处理剩余的元素,如果左半边还有剩余,全部加入tempwhile (begin1 <= end1) {temp.push_back(array[begin1++]);}// 如果右半边还有剩余,全部加入tempwhile (begin2 <= end2) {temp.push_back(array[begin2++]);}// 拷贝回原数组// 注意:这里使用i - left是因为temp是从0开始的,而原数组的子区间是从left开始的for (int i = left; i <= right; i++) {array[i] = temp[i - left];}
}

这段代码实现了一个标准的归并排序算法流程,包括了递归划分区间、合并已排序区间,以及最终将排序结果拷贝回原数组的操作。通过这样的分治策略,归并排序能够高效地对整个数组进行排序。

在这里插入图片描述

归并排序的性能

归并排序(Merge Sort)在性能上的主要特点如下:

  1. 时间复杂度

    • 归并排序的时间复杂度在平均情况、最好情况以及最坏情况下均为O(n log n),其中n是数组中的元素数量。这是因为归并排序总是将数组分成两半处理,每一层递归深度为log n层,每层需要线性时间n来合并,故总时间为n * log n。
    • 这种时间复杂度保证了归并排序对于大规模数据集来说是非常高效的,且性能稳定,不依赖于原始数据的排列状态。
  2. 空间复杂度

  • 归并排序的空间复杂度为O(n),主要原因是需要一个与原数组相同大小的临时数组来合并两个子数组。这是归并排序的一个缺点,尤其是在处理极大规模数据时,额外的空间需求可能成为一个限制因素。
  • 不过,也有原地归并排序(In-place Merge Sort)的变体尝试减少空间复杂度,但它们通常比标准归并排序更复杂且可能牺牲一些性能。
  1. 稳定性
  • 归并排序是一种稳定的排序算法,即相等的元素在排序前后相对位置不变。这是因为合并过程中,当遇到两个相等的元素时,总是先取左边子数组的元素,保持了稳定性。
  1. 适用场景
  • 归并排序适合于数据量较大的排序场景,特别是对稳定性有要求的情况。它也是外部排序算法的基础,例如在处理磁盘文件排序时非常有用,因为可以将数据分块读入内存进行排序后再合并。
    • 对于内存受限环境,归并排序可能不是最佳选择,此时空间效率更高的算法(如原地排序算法)可能更为合适。
  1. 比较与其他排序算法
  • 相较于快速排序,归并排序虽然两者的时间复杂度相同,但归并排序是稳定的且时间复杂度不会受输入数据的影响。而快速排序在最坏情况下可能退化到O(n^2),尽管通过随机化选取枢轴可以很大程度上避免这种情况。
  • 与插入排序、冒泡排序等简单排序算法相比,归并排序的时间复杂度明显更低,尤其在处理大量数据时优势显著,但它们的空间复杂度更低,更适合小数据集或几乎已排序的数据。

综上所述,归并排序在时间效率上表现出色,尤其适合大规模数据集的排序,但在空间使用上较为奢侈,这是使用时需要考虑的主要权衡点。

一些小总结

我们抽离一下,看看这个代码的结构:

void merge_sort(std::vector<int>& array,int left,int right,std::vector<int>& temp){if (left >= right)return;//分int mid = left + (right - left) / 2;merge_sort(array, left, mid, temp);merge_sort(array, mid + 1, right, temp);temp.clear(); //保证是在一个干净的环境里进行工作//合int begin1 = left;int end1 = mid;int begin2 = mid + 1;int end2 = right;while (begin1 <= end1 && begin2 <= end2){if (array[begin1] > array[begin2]){temp.push_back(array[begin2++]);}else{temp.push_back(array[begin1++]);}}//剩下的多出来的也放进去while (begin1 <= end1){temp.push_back(array[begin1++]);}while (begin2 <= end2){temp.push_back(array[begin2++]);}//铐回原来的数组for (int i = left; i <= right; i++){array[i] = temp[i - left];}}

我们可以得出这样的结构:

void merge_sort(std::vector<int>& array,int left,int right,std::vector<int>& temp){//终止条件if (left >= right)return;//分int mid = left + (right - left) / 2;merge_sort(array, left, mid, temp);merge_sort(array, mid + 1, right, temp);//操作

递归在前,操作在后,这是一个标准的后序遍历,所以归并排序的思想基础是基于二叉树的后序遍历

我们来看看快速排序:


```cpp
/*** 快速排序函数* @param array 待排序的整型数组指针* @param begin 数组的起始索引* @param end 数组的结束索引*/
void quick_sort_part(int *array, int begin, int end) {int left = begin; // 初始化左指针int right = end;  // 初始化右指针// 当左指针小于右指针时继续排序if (left >= right) {return;}int stander = left; // 选择数组起始位置的元素作为基准值// 外层循环:直到左右指针相遇while (left < right) {// 移动右指针,直到找到一个小于等于基准值的元素或右指针到达左指针while (right > left && array[stander] <= array[right]) {right--;}// 移动左指针,直到找到一个大于等于基准值的元素或左指针到达右指针while (left < right && array[stander] >= array[left]) {left++;}// 如果左右指针未相遇,则交换这两个元素的位置if (left < right) {swap(array[left], array[right]);}}// 将基准值放到最终位置(左右指针相遇的位置)swap(array[stander], array[left]);// 对基准值左边的子数组进行快速排序quick_sort_part(array, begin, left - 1);// 对基准值右边的子数组进行快速排序quick_sort_part(array, left + 1, end);
}

提出框架:


```cpp
/*** 快速排序函数* @param array 待排序的整型数组指针* @param begin 数组的起始索引* @param end 数组的结束索引*/
void quick_sort_part(int *array, int begin, int end) {//初始化操作// 当左指针小于右指针时继续排序,停止条件if (left >= right) {return;}//中间操作// 对基准值左边的子数组进行快速排序quick_sort_part(array, begin, left - 1);// 对基准值右边的子数组进行快速排序quick_sort_part(array, left + 1, end);
}

发现了吗,快速排序基于二叉树前序遍历的思想。

如果刷题刷的多的话,会发现大部分的问题是基于二叉树,或者n叉树的框架来的,所以有意识的积累,对解题有帮助。

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

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

相关文章

杂项——STM32ZET6要注意的一些问题——高级定时器问题和PB3,PB4引脚问题

ZET6可能会用到定时器&#xff0c;高级定时器要输出PWM要加上这样一行代码&#xff0c;否则无法正常输出PWM波 TIM_CtrlPWMOutputs(TIM8, ENABLE); // 主输出使能&#xff0c;当使用的是通用定时器时&#xff0c;这句不需要 ZET6中PB3,PB4引脚默认功能是JTDO和NJTRST,如果想将…

六一和侄子拼lego颗粒二维画

一、从泥巴到高科技&#xff1a;儿时玩具的变迁 在我童年的记忆里&#xff0c;最快乐的时光往往与简单的玩具和泥巴有关。在那个没有智能手机和电子游戏的年代&#xff0c;泥巴是我们的乐园&#xff0c;而玩具则是我们的伴侣。 小时候&#xff0c;泥巴是我们的创造力的源泉。…

Go跨平台编译

1.编译windows平台运行程序 # windows env GOOSwindows GOARCHamd64 go build main.go2.编译linux平台运行程序 # linux env GOOSlinux GOARCHamd64 go build main.go 3.编译macos平台运行程序 # macos env GOOSdarwin GOARCHamd64 go build main.go 编译结果:

Python3 match-case 语句

前言 本文主要介绍match-case语句与switch-case的区别&#xff0c;及match-case语句的基本用法。 文章目录 前言一、switch-case 和match-case的区别二、match-case的基本用法1、可匹配的数据类型2、多条件匹配3、通配符匹配 一、switch-case 和match-case的区别 C语言里面s…

Git操作笔记

学git已经好多次了。但是还是会忘记很多的东西&#xff0c;一些常用的操作命令和遇到的bug以后在这边记录汇总下 一.github图片展示 图片挂载&#xff0c;我是创建了一个库专门存图片&#xff0c;然后在github的md中用专用命令展示图片&#xff0c;这样你的md就不会全是文字那…

【C语言】文件操作(中卷)

前言 在文件操作&#xff08;上卷&#xff09;中&#xff0c;讲到的主要都是正式文件操作开始之前的前置知识&#xff0c;而这一卷中&#xff0c;我们将开始正式地操作文件。 在上卷中我们已经说到&#xff0c;stdin stdout stderr是三个C语言程序启动时默认打开的流。这三个流…

HarmonyOS应用开发学习历程(1)初识DevEco Studio

1.create project Bundle name&#xff1a;包名&#xff0c;标识应用程序&#xff0c;默认应用ID也使用该名 Compile SDK&#xff1a;编译时API版本 2.工程目录 AppScope&#xff1a;应用全局所需资源 entry&#xff1a;应用的主模块&#xff0c;含代码、资源 hvigor&#…

TimeDao-一篇文章了解清楚Subspace项目

1 项目简介 什么是Subspace网络&#xff1f; Subspace是为下一波加密创建者构建的第四代区块链。旨在实现web3规模扩容。 Subspace允许开发者以互联网规模运行 Web3 应用。它提供了一个简单的接口&#xff0c;用于快速部署按需求自动扩展的多链去中心化应用。Subspace由一个…

神经网络与深度学习——第7章 网络优化与正则化

本文讨论的内容参考自《神经网络与深度学习》https://nndl.github.io/ 第7章 网络优化与正则化 网络优化与正则化 网络优化 网络结构多样性 高维变量的非凸优化 神经网络优化的改善方法 优化算法 小批量梯度下降 批量大小选择 学习率调整 学习率衰减 学习率预热 周期性学习率调…

HCIP-Datacom-ARST自选题库__EBGP【18道题】

一、单选题 1.在排除EBGP邻居关系故障时&#xff0c;你发现两台直连设备使用Loopback口建立连接&#xff0c;故执行display current-configurationconfiguration bgp查看peer ebgp-max-hop hop-count的配置&#xff0c;下列哪项说法是正确的? hop-count必须大于2 hop-count…

8条黄金准则,解决API安全问题

API&#xff08;应用程序编程接口&#xff09;是现代软件开发中不可或缺的一部分。它们允许不同的应用程序之间共享数据和功能&#xff0c;从而促进了软件系统的整合和互操作性。然而&#xff0c;随着API使用的普及&#xff0c;安全性问题也开始浮出水面。 API安全是指保护API免…

Windows 剪映专业版 v5.9.0 解锁VIP、解除限制功能!

介绍 该脚本具备多项高级功能&#xff0c;包括人像抠图、会员专属模板、超清画质以及素材预设。会员可使用的功能均通过此脚本解锁。 解锁剪映软件会员功能&#xff0c;包括人像抠图、会员模板、超清画质以及素材预设等。 在标准操作流程中&#xff0c;用户首先在电脑端启动…

系统架构设计师【第12章】: 信息系统架构设计理论与实践 (核心总结)

文章目录 12.1 信息系统架构基本概念及发展12.1.1 信息系统架构的概述12.1.2 信息系统架构的发展12.1.3 信息系统架构的定义 12.2 信息系统架构12.2.1 架构风格12.2.2 信息系统架构分类12.2.3 信息系统架构的一般原理12.2.4 信息系统常用4种架构模型12.2.5 企业信息系…

大模型应用:Prompt-Engineering优化原则

1.Prompt-Engineering 随着大模型的出现及应用&#xff0c;出现了一门新兴“技术”&#xff0c;该技术被称为Prompt-Enginerring。Prompt Engineering即提示工程&#xff0c;是指在使用大语言模型时&#xff0c;编写高效、准确的Prompt(提示词)的过程。通过不同的表述、细节和…

【JavaEE 进阶(二)】Spring MVC(下)

❣博主主页: 33的博客❣ ▶️文章专栏分类:JavaEE◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多进阶知识 目录 1.前言2.响应2.1返回静态界面2.2返回数据2.3返回HTML代码 3.综合练习3.1计算器3.2用户登…

ROS简介

ROS概念 学习路线 操作系统 Linux环境下编译执行c文件&#xff08;需安装vim超文本编辑器&#xff09; sudo g MyCoding.cpp -o CodeTest //生成一个名字为CodeTest的可执行文件 sudo ./CodeTest //执行c文件版本问题 ROS Melodic Morenia 和 ROS Noetic Ninjemys 是…

基于Django的博客系统之登录增加忘记密码(八)

需求 描述&#xff1a; 用户忘记密码时&#xff0c;提供一种重置密码的方法&#xff0c;以便重新获得账户访问权限。规划&#xff1a; 创建一个包含邮箱输入字段的表单&#xff0c;用于接收用户的重置密码请求。用户输入注册时使用的邮箱地址&#xff0c;系统发送包含重置密码…

CTF本地靶场搭建——基于阿里云ACR实现动态flag题型的创建

接上文&#xff0c;这篇主要是结合阿里云ACR来实现动态flag题型的创建。 这里顺便也介绍一下阿里云的ACR服务。 阿里云容器镜像服务&#xff08;简称 ACR&#xff09;是面向容器镜像、Helm Chart 等符合 OCI 标准的云原生制品安全托管及高效分发平台。 ACR 支持全球同步加速、…

如何恢复 Android 设备上丢失的照片

由于我们的大量数据和日常生活都存储在一台设备上&#xff0c;因此有时将所有照片本地存储在 Android 智能手机或平板电脑上可能是一种冒险行为。无论是由于意外&#xff08;损坏、无意删除&#xff09;&#xff0c;还是您认识的人翻看您的设备并故意删除了您想要保留的照片&am…

从0开始学统计-什么是回归?

1.什么是回归&#xff1f; 回归&#xff08;Regression&#xff09;是统计学中一种用于探索变量之间关系的分析方法。它主要用于预测一个或多个自变量&#xff08;输入变量&#xff09;与因变量&#xff08;输出变量&#xff09;之间的关系。在回归分析中&#xff0c;我们尝试根…