排序(5)——归并排序

六、归并排序

1.简介

        归并排序也是一种很经典的排序算法,采用分治的思想方法进行数据的处理。归并讲究的是先拆后合,也就是分治中的分而治之。在拿到一组数据后,程序会将整个数据进行不断拆分直至有序,因为单个元素必然有序,所以归并排序最后拆分形成的组每组只有一个元素。对于这些已经有序的组别,再进行治,就是合并两个有序序列。如此采取先分再合的策略使得一组数据最后成为有序。

2.思路与代码

(1)归并排序---递归

         首先需要理解归并排序的工作流程。归并排序首先对元素进行分割,在不断的分割后形成了一批只有一个元素的组,这些组因为只有一个元素,所以可以被认为是有序的。在将所有元素都分割成为有序序列后,便可以放心地进行合并了。合并就是将两个有序数组合并为一个有序数组,通过合并操作,我们可以将这些有序序列不断合并最后完成整个数组的排序。

        

        接下来梳理一下归并排序的流程和一些要点,对于一个数组a:

        ①我们仔细观察会发现,所谓分割就是将数据的处理范围进行缩小,从最初的整个数组,每一次简化一半的规模直到规模为一个元素,此时相当于完成了分割。然后就是对这些小规模的数据组进行合并再合并完成排序。分割部分实际就是简单的收缩范围,并未对数据做出什么处理;合并部分则是有一种逆着分割的感觉,并对数据进行了关键的排序。如此可以发现,归并排序实际就是将数组不断分为左右两部分,再分割结束后再进行逐层处理,这就是我们之前所说过的后序遍历的情况。所以我们在递归实现归并排序的过程中可以参考后序遍历的模板。

        ②合并时是要完成排序操作的,所以对两个有序数组进行合并生成一个有序数组也是我们需要解决的问题。这个问题并不困难,只需要借助辅助空间,比较两个数组元素小的进入辅助数组即可。这也意味着我们需要额外的一个临时数组来帮助我们进行数组的合并操作。

        ③完成代码时,我们因为既需要递归,又需要开辟数组,所以最佳解决方案就是将二者分离为两个函数,这样就可因避免递归导致反复开空间,难以管理。

//归并排序
void _MergeSort(int* a, int* tmp, int begin, int end)
{if (begin >= end){return;}int mid = (end + begin) / 2;_MergeSort(a, tmp, begin, mid);_MergeSort(a, tmp, mid + 1, end);int head1 = begin;int head2 = mid + 1;int cur = begin;while (head1 <= mid && head2 <= end){if (a[head1] > a[head2]){tmp[cur++] = a[head2++];}else{tmp[cur++] = a[head1++];}}while (head1 <= mid){tmp[cur++] = a[head1++];}while (head2 <= end){tmp[cur++] = a[head2++];}for (int i = begin; i <= end; i++){a[i] = tmp[i];}
}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);
}

(2)归并排序---非递归

        在快速排序的递归改非递归时,我们指出递归变为非递归一般有两种途径:栈或者循环。像是快排一般的前序遍历使用栈是很顺手的,因为前序遍历先处理根结点,在根结点处理完后才在栈中存储接下来要处理的数据,这时根结点处理完成早已出栈,并无什么影响。但是归并排序属于后序遍历,相当于我们需要借助根结点得到接下来处理的数据,然后根结点不可以释放,因为最后仍需要处理根结点(合并)。相当于归并排序根结点需要二次使用,虽然我们可以使用两个栈,或者在入栈前先找到下一个区间再入栈等方法解决这个问题,但是嘛该用循环就要用,这里我们就用循环实现归并排序的非递归。

        因为归并排序的后序遍历注定了分割是“虚假的”,即我们可以主观认为数组中元素已经被分割好了。所以我们给出一个变量gap来标识合并过程下两个数组的规模。每一轮合并结束后gap就翻倍,这与递归下的逻辑相同。

        在具体的一轮排序中,需要很清晰两组数据的起始和终止位置。以变量i作为第一组数据的起始下标,因为一组数据规模是gap,所以第一组数据终止下标为i+gap-1。同理可以得到第二组数字的区间为[i+gap,i+2*gap-1]。当需要处理下一对时,给i加上2*gap即可。明确了这一点后,合并过程的排序逻辑就与递归无异了。

        最后需要注意的一点是不完全数组。当执行到一轮合并的最后一组数据时,因为我们的起始位置和终止位置都是根据i和gap计算所得的,所以需要判断并限制越界访问。当end1或begin2越界时,就说明不存在第二组数据,所以也就不需要进行合并操作了,直接终止循环即可;当只有end2越界时说明第二组数据和第一组数据规模不同,为了防止越界,只需要把第二组数据的终止下标end2人为改为整个数组最后一个元素下标就可以防止越界了。

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 += 2 * gap){int begin1 = i, end1 = i + gap - 1;//归并中第一个区间的范围int begin2 = i + gap, end2 = i + 2 * gap - 1;//归并中第二个区间的范围int head1 = begin1, head2 = begin2;int cur = begin1;if (end1 >= n || begin2 >= n)//数据只存在于第一个区间中,而第二个区间没有数据,这时无需归并(因为只有一组数据已然有序){break;}if (end2 >= n)//第二个区间数据不满,此时需要对第二个区间进行调整,然后再归并{end2 = n - 1;//这种情况只会在最后一组出现,所以直接赋值end2为序列最后一个元素下标即可}while (head1 <= end1 && head2 <= end2){if (a[head1] < a[head2]){tmp[cur++] = a[head1++];}else{tmp[cur++] = a[head2++];}}while (head1 <= end1){tmp[cur++] = a[head1++];}while (head2 <= end2){tmp[cur++] = a[head2++];}for (int i = begin1; i <= end2; i++){a[i] = tmp[i];}}gap *= 2;}free(tmp);
}

3.复杂度与稳定性分析

(1)时间复杂度

        因为归并排序和快速排序都是类似于二叉树的分治过程,所以归并排序的时间复杂度和快速排序的计算方法一样,每一层都是一次数组的遍历,不同的是快排层数在log_2nn之间不确定,而归并排序层数很明显是log_2n层。

        因此归并排序时间复杂度为O(n*logn)

(2)空间复杂度

        归并排序使用了一个临时数组作为辅助与拷贝,再加上递归开销,其空间开销为n+log_2n,根据大O渐进表示法,其空间复杂度为O(n)

        我们所掌握的诸如快排、堆排序之类的排序算法一般称为内排序,因为他们都是在对内存中的数据进行排序。而归并排序虽然有O(n)的空间复杂度,但这也意味着归并排序可以应用于外排序,即对文件的数据排序中。在用归并排序进行外排序的时候,我们可以将一个新文件作为额外的空间使用,于是就可以在文件中排序了。

(3)稳定性

        归并排序是稳定的。

        归并排序对数据是整块整块处理的,可以说泾渭分明,相对位置维持的很好,所以是稳定的。

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

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

相关文章

教你怎么给接口定义错误码

错误码一般包括三个要素&#xff1a; 前缀标识&#xff1a;区分错误类型&#xff0c;比如请求不合法&#xff0c;还是服务器处理错误。模块标识&#xff1a;区分到底是哪个模块的错误。错误代码&#xff1a;区分具体是什么问题。 从错误码的符号组成上看又分为三类&#xff1…

网络发展历程及SD-WAN的优势

自网络出现以来&#xff0c;经历了不同时期的发展阶段&#xff0c;而SD-WAN作为网络发展的新趋势&#xff0c;以其独特的优势满足了当今企业对于高效、灵活和成本控制的网络需求。 一、网络发展历程 &#xff08;1&#xff09;初始阶段&#xff1a;专线网络 从1980年代到2000…

VBA数据库解决方案第八讲:SQL语句及打开记录集

《VBA数据库解决方案》教程&#xff08;版权10090845&#xff09;是我推出的第二套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;是学完字典后的另一个专题讲解。数据库是数据处理的利器&#xff0c;教程中详细介绍了利用ADO连接ACCDB和EXCEL的方法…

flowable 设置自定义属性教程

概述 由于工作需要给flowable工作流设计器添加自定义属性&#xff0c;以满足功能实现。所以这篇文章介绍下用flowable 开源的的flowable-ui 前端添加自定义属性&#xff0c;后端解析属性值的例子。 技术栈 序号技术点名称版本1Flowable6.8.0 使用的是flowable6.8.0 版的代码…

画质和场景双需求下,海信电视U8KL的变与不变

又到一年春节&#xff0c;最近几年大家过年的方式越来越丰富&#xff0c;但是跟家人在一起看春晚依然是主流&#xff0c;电视也是过年不可或缺的家庭成员。 当然&#xff0c;随着大家对生活品质的要求更高&#xff0c;对电视的要求也变得更高了。比如&#xff0c;现在春晚直播…

爬虫笔记(三):实战qq登录

咳咳&#xff0c;再这样下去会进橘子叭hhhhhh 以及&#xff0c;这个我觉得大概率是成功的&#xff0c;因为测试了太多次&#xff0c;登录并且验证之后&#xff0c;qq提醒我要我修改密码才可以登录捏QAQ 1. selenium 有关selenium具体是啥&#xff0c;这里就不再赘述了&#x…

leetcode c++ 超出内存限制

给两个主要原因&#xff0c;这两个原因&#xff0c;如果在递归或者循环里就很容易导致内存超出限制 首先就是误用 如果只是变量的变化实现的话&#xff0c;或者-就可以了&#xff0c;不需要写成AAx的形式&#xff0c;那样会重新开辟一块内存 其次就是值传递 值传递也是会开辟…

在Linux中对Nginx进行安全加固

准备工作 在IP为x.x.x.x的服务器上安装nginx&#xff0c;确保Linux系统为nginx环境。 检查nginx是否配置nginx账号锁定策略 配置nginx账号锁定策略&#xff0c;降低被攻击概率。 第一步&#xff0c;查看nginx的锁定状态。 命令&#xff1a;passwd -S nginx 若结果出现“P…

【gcc】webrtc发送侧计算 丢包率

大神的分析 : 提到: 每当收到cc-feedback或者收到RR-report的时候就能统计出丢包率,在cc-controller中就会调用SendSideBandwidthEstimation::UpdatePacketsLost()去更新丢包率,同时进行码率预估 G:\CDN\rtcCli\m98\src\modules\congestion_controller\goog_cc\send_side_b…

今日arXiv最热NLP大模型论文:像人一样浏览网页执行任务,腾讯AI lab发布多模态端到端Agent

Agent的发展成为了LLM发展的一个热点。只需通过简单指令&#xff0c;Agent帮你完成从输入内容、浏览网页、选择事项、点击、返回等一系列需要执行多步&#xff0c;才能完成的与网页交互的复杂任务。 比如给定任务&#xff1a;“搜索Apple商店&#xff0c;了解iPad智能保护壳Sm…

Qt中的绝对位置与相对位置

在Qt的开发中&#xff0c;有时候需要获取鼠标点击的位置的坐标。在Qt中坐标分为相对坐标和绝对坐标&#xff1b;相对坐标是以控件的左上角为&#xff08;0&#xff0c;0&#xff09;点的坐标系的坐标&#xff0c;绝对坐标是以显示器屏幕的左上角为&#xff08;0&#xff0c;0&a…

蓝桥杯-常用STL(二)

常用STL &#x1f388;1.集合&#x1f388;2.set的基础使用&#x1f52d;2.1引入库&#x1f52d;2.2插入元素&#x1f52d;2.3删除元素&#x1f52d;2.4判断元素是否存在&#x1f52d;2.5遍历元素&#x1f52d;2.6清空 &#x1f388;3.set与结构体 &#x1f388;1.集合 &#x…

【Algorithms 4】算法(第4版)学习笔记 04 - 2.1 初级排序算法

文章目录 前言参考目录学习笔记1&#xff1a;前置说明1.1&#xff1a;全序关系1.2&#xff1a;Comparable API 实现 demo1.3&#xff1a;排序算法模板2&#xff1a;选择排序2.1&#xff1a;内循环实现过程拆解2.2&#xff1a;代码实现2.3&#xff1a;特点3&#xff1a;插入排序…

【kubernetes】集群网络(二):Flannel的VxLan、Host-GW模式

文章目录 1 Pod的IP地址的分配2 CNI3 Flannel3.1 Flannel的安装3.2 VxLan3.3 Host-GW 4 总结 1 Pod的IP地址的分配 当节点上只安装了docker&#xff0c;则会用veth pairdocker0实现单个节点上容器之间的通信&#xff0c;并且这些容器都在同一个IP段&#xff0c;如果不修改&…

(2024,SaFaRI,双三上采样和 DFT,空间特征和频率特征)基于扩散模型的图像空间和频率感知恢复方法

Spatial-and-Frequency-aware Restoration method for Images based on Diffusion Models 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 3. 方法 3.1 修改数据保真度 3.2 …

JMeter GUI:测试计划和工作台

什么是测试计划&#xff1f; 测试计划是您添加 JMeter 测试所需元素的地方。 它存储运行所需测试所需的所有元素&#xff08;如线程组、计时器等&#xff09;及其相应的设置。 下图显示了测试计划的示例 测试计划是您添加 JMeter 测试所需元素的地方。 它存储运行所需测试…

Modbus协议学习第六篇之基于libmodbus库的示例程序(可以联合Modbus模拟仿真软件进行调试)

前置工作 学了这么多Modbus的知识&#xff0c;如果不进行实际的操作&#xff0c;总感觉懂的不透彻。基于此&#xff0c; 本篇博文就带各位读者来了解下如何通过编写程序来模拟与Modbus Slave仿真软件的通讯。当然了&#xff0c;这里有两个前提&#xff0c;如下&#xff1a; 1.请…

【深度强化学习】Python:OpenAI Gym-CarRacing 自动驾驶 | 提供项目完整代码 | 车道检测功能 | 路径训练功能 | 车辆控制功能

💭 写在前面:本篇是关于 OpenAI Gym-CarRacing 自动驾驶项目的博客,面向掌握 Python 并有一定的深度强化学习基础的读者。GYM-Box2D CarRacing 是一种在 OpenAI Gym 平台上开发和比较强化学习算法的模拟环境。它是流行的 Box2D 物理引擎的一个版本,经过修改以支持模拟汽车…

一键部署幻兽帕鲁服务器免费一年方案

一、背景介绍 简单讲一下历程&#xff0c;幻兽帕鲁从在1月19日上线&#xff0c;24小时内在线人数峰值便突破200万&#xff0c;作为2024年第一款现象级游戏&#xff0c;《幻兽帕鲁》上线后&#xff0c;由于人数太多&#xff0c;频现服务器过载导致游戏卡顿掉线的情况。为了能够…

【GitHub项目推荐--大语言模型课程】【转载】

Large Language Model Course Large Language Model Course&#xff08;大型语言模型课程&#xff09;是一个开源项目&#xff0c;该课程分为三个部分&#xff1a; LLM 基础&#xff1a;涵盖了数学、Python 和神经网络的基础知识。 LLM 科学家&#xff1a;专注于学习如何使用…