<数据结构>NO11.归并排序|递归|非递归|优化

在这里插入图片描述

文章目录

  • 归并排序
    • 递归写法
    • 非递归写法
      • 修正方案1.归并一段拷贝一段
      • 修正方案2.修正区间
    • 算法优化
    • 算法分析
  • 归并排序的应用
    • 外排序和内排序

归并排序

递归写法

思路:
如果给出两个有序数组,我们很容易可以将它们合并为一个有序数组。因此当给出一个无序数组时,我们先将它们均分为两组有序数组,在将这两组有序数组合并为一个有序数组;而将原数组分成2组 有序数组的思路又是归并排序

递归的结束条件是什么?
当递归区间只有一个元素时结束递归

使用归并排序排3 5 7 6 9 2 10 8详细过程如下:

在这里插入图片描述

在这里插入图片描述

代码

void _MergeSort(int* arr, int* tmp, int begin, int end)
{//递归终止条件--区间只有一个数if (begin == end)return;//将数据均分未2组int mid = (begin + end) >> 1;int i = begin;//使两组均有序_MergeSort(arr, tmp, begin, mid);_MergeSort(arr, tmp, mid + 1, end);//有序数组归并---归并到tmp数组中int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2])tmp[i++] = arr[begin1++];elsetmp[i++] = arr[begin2++];}while (begin1 <= end1)	tmp[i++] = arr[begin1++];while (begin2 <= end2)	tmp[i++] = arr[begin2++];//数据拷贝回元素组中memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
//归并排序
//时间复杂度O(nlogn)
//空间复杂度O(n+logn)
void MergeSort(int* arr, int sz)
{int* tmp = (int*)malloc(sizeof(int) * sz);//tmp作为临时数组存放有序数组合并后的数据_MergeSort(arr, tmp, 0, sz - 1);		  //因为要借助临时数组所以构造子函数free(tmp);
}

注意:

  1. 由于mid向上取整,所以不存在递归区间不存在的情况,结束条件只有区间只有一个值

  2. 每次拷贝时是从arr+begin处开始拷贝


非递归写法

归并排序的非递归写法不能和快速排序一样通过栈实现,如果使用栈存归并的区间,那么每次取出区间归并时栈顶元素出栈。这就导致了栈中不在存放有关该区间的任何信息,但是归并排序要求对区间的值进行保存,所以不能使用栈来实现

思路:
对数组依次进行一一归并、两两归并、四四归并,直至数组最终有序。

在这里插入图片描述

代码

//归并排序非递归
void MergeSortNonR(int* arr, int sz)
{int* tmp = (int*)malloc(sizeof(int) * sz);//临时数组用来存放分组排序的结果assert(tmp);int gap = 1;//从间隔为1开始归并while (gap < sz){int j = 0;for (int i = 0; i < sz; i += 2 * gap)//以gap为间隔归并完一组后归并下一组{//有序数组归并---归并到tmp数组中int begin1 = i, end1 = begin1 + gap - 1;int begin2 = end1 + 1, end2 = begin2 + gap - 1;//归并区间while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2])tmp[j++] = arr[begin1++];elsetmp[j++] = arr[begin2++];}while (begin1 <= end1)	tmp[j++] = arr[begin1++];while (begin2 <= end2)	tmp[j++] = arr[begin2++];}//拷贝回元素组中memcpy(arr, tmp, sizeof(int) * sz);gap *= 2;}  

运行结果
在这里插入图片描述

看似没有问题,但是当我们在原数组基础上添加1个数据再排序时程序会崩溃在这里插入图片描述

我们走读代码看看哪里出了问题
在这里插入图片描述

gap=2,i = 8时,begin1 = 8, end1 = 9,此时在访问arr[end1]就会越界。同理,如果数组元素是10个,那么当gap=4,i = 8时,begin1 = 8, end1 = 11,此时访问arr[end1]同样会越界,所以只有当数组元素为2的幂次方时,上述排序才正确。因此我们需要对上述代码进行修改


修改:数组越界一共有3种情况,分别是end1越界,begin1越界,end2越界
在这里插入图片描述

修正方案1.归并一段拷贝一段

上面已经提过了当数据个数为9个时,两两归并时会访问arr[9], 并且将该值拷贝到不属于我们创建的堆空间中。造成了越界访问
为了避免将原数组外的空间拷贝我们选择归并一段拷贝一段,只拷贝我们当趟已经归并的区间.
如果end1或者begin2越界,直接break不归并这段区间也就拷贝,剩余的数据交给gap增大的下一次归并;如果遇见end2越界,则修正end2为sz-1,归并区间[begin1,end1]和[begin2, end2]
在这里插入图片描述

代码

//归并排序非递归(归并一趟拷贝一趟)
void MergeSortNonR(int* arr, int sz)
{int* tmp = (int*)malloc(sizeof(int) * sz);//临时数组用来存放分组排序的结果assert(tmp);int gap = 1;//从间隔为1开始归并while (gap < sz){int j = 0;for (int i = 0; i < sz; i += 2 * gap){//有序数组归并---归并到tmp数组中int begin1 = i, end1 = begin1 + gap - 1;int begin2 = end1 + 1, end2 = begin2 + gap - 1;//end1越界或者begin2越界跳出剩下为归并的数据交给下一次处理if (end1 >= sz || begin2 >= sz){break;}//end2越界,修正end2if (begin2 < sz && end2 >= sz){end2 = sz - 1;}while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2])tmp[j++] = arr[begin1++];elsetmp[j++] = arr[begin2++];}while (begin1 <= end1)	tmp[j++] = arr[begin1++];while (begin2 <= end2)	tmp[j++] = arr[begin2++];//归并一段拷贝一段memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));}//拷贝回元素组中//memcpy(arr, tmp, sizeof(int) * sz);gap *= 2;}  

修正方案2.修正区间

方案1是归并一段拷贝一段,越界的那一部分区间不进行处理,让下一趟归并处理上一趟违为归并的数据。
方案2是先将越界的那一部分区间进行修正(修正的区间可能合法也可能不合法),在对区间进行以gap为间隔的归并,每次归并完整个数组后再拷贝(方案1归并完一个区间就拷贝)。

代码

//归并排序非递归(修正区间)
void MergeSortNonR(int* arr, int sz)
{int* tmp = (int*)malloc(sizeof(int) * sz);//临时数组用来存放分组排序的结果assert(tmp);int gap = 1;//从间隔为1开始归并while (gap < sz){int j = 0;for (int i = 0; i < sz; i += 2 * gap){//有序数组归并---归并到tmp数组中int begin1 = i, end1 = begin1 + gap - 1;int begin2 = end1 + 1, end2 = begin2 + gap - 1;//end1越界if (end1 >= sz){//end1修正为边界end1 = sz - 1;//[begin2, end2]修正为不存在的区间begin2 = sz;end2 = sz - 1;}else if (begin2 >= sz){//修正为不存在的区间begin2 = sz;end2 = sz - 1;}else if (end2 >= sz){end2 = sz - 1;}while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2])tmp[j++] = arr[begin1++];elsetmp[j++] = arr[begin2++];}while (begin1 <= end1)	tmp[j++] = arr[begin1++];while (begin2 <= end2)	tmp[j++] = arr[begin2++];}//拷贝回元素组中memcpy(arr, tmp, sizeof(int) * sz);gap *= 2;}  free(tmp);
}

算法优化

假设我们排100000个数据,归并排序每次排序都会进行递归调用,每一个区间都会递归调用2次,因此当递归区间长度减小到某一个数时,该递归区间在进行递归调用时递归的次数就会非常多在这里插入图片描述

总共递归 2 h − 1 次,最后一层递归次数占了全部的 % 50 , 倒数第二层的递归调用占了全部的 % 25 , , 倒数第三层递归调用占了全部的 % 12.5 , 因此最后三层占了所有递归调用次数的 % 87.5 总共递归2^{h}-1次,最后一层递归次数占了全部的\%50,倒数第二层的递归调用占了全部的\%25,,倒数第三层递归调用占了全部的\%12.5,因此最后三层占了所有递归调用次数的\%87.5 总共递归2h1次,最后一层递归次数占了全部的%50,倒数第二层的递归调用占了全部的%25,,倒数第三层递归调用占了全部的%12.5,因此最后三层占了所有递归调用次数的%87.5

当递归区间元素个数为10时,还会接着递归3层,因此我们可以进行小区间优化当递归区间长度小于等于10时,直接进行插入排序,这样可以大大减少递归调用次数。

优化代码

void _MergeSort(int* arr, int* tmp, int begin, int end)
{//递归终止条件--区间只有一个数if (begin == end)return;//小区间优化if (end - begin + 1 <= 10){InsertSort(arr + begin, end - begin + 1);return;}//将数据均分未2组int mid = (begin + end) >> 1;int i = begin;//使两组均有序_MergeSort(arr, tmp, begin, mid);_MergeSort(arr, tmp, mid + 1, end);//有序数组归并---归并到tmp数组中int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2])tmp[i++] = arr[begin1++];elsetmp[i++] = arr[begin2++];}while (begin1 <= end1)	tmp[i++] = arr[begin1++];while (begin2 <= end2)	tmp[i++] = arr[begin2++];//拷贝回元素组中memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
//归并排序
void MergeSort(int* arr, int sz)
{int* tmp = (int*)malloc(sizeof(int) * sz);//tmp作为临时数组存放有序数组合并后的数据_MergeSort(arr, tmp, 0, sz - 1);		  //因为要借助临时数组所以构造子函数free(tmp);
}

优化结果
在这里插入图片描述


算法分析

归并排序时间复杂度空间复杂度
递归 O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n) O(n)
非递归 O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n) O(n)

归并排序的应用

外排序和内排序

对内存中的数据进行排序称为内排序,对外存储器上的数据进行排序称为外排序。
现代计算机的内存通常在 8 − 16 G B 8-16GB 816GB之间可以存储的整数在 21 亿 − 42 亿 21亿-42亿 21亿42亿之间,如果我们要排 200 亿 200亿 200亿的数据量怎么处理?

  1. 将数据放在文件中,将大文件分为n份小文件使每个小文件存储的数据量可以放在内存中排序
  2. 对将每个小文件的数据放在内存中进行排序(归并、快排),使得每个小文件有序
  3. 对n个小文件外使用归并排序进行外排序

注意:
外排序只能使用归并排序,因此文件数据不能通过下标访问随机读取,文件指针习惯顺序读写,而归并排序不需要下标访问

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

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

相关文章

http和https的区别(面试题)

概念 Http&#xff1a;HTTP协议是Hyper Text Transfer Protocol&#xff08;超文本传输协议&#xff09;的缩写。HTTP 协议和 TCP/IP 协议族内的其他众多的协议相同&#xff0c; 用于客户端和服务器之间的通信。从WWW服务器传输超文本到本地浏览器的传输协议&#xff0c;它可以…

一个月学通Python(二十):Python制作报表(Web开发)

专栏介绍 结合自身经验和内部资料总结的Python教程,每天3-5章,最短1个月就能全方位的完成Python的学习并进行实战开发,学完了定能成为大佬!加油吧!卷起来! 全部文章请访问专栏:《Python全栈教程(0基础)》 文章目录 专栏介绍制作报表导出Excel报表导出PDF报表生成前端…

python 写个excle表格数据导入mysql数据的服务

下面是一个使用Python将Excel表格数据导入MySQL数据库的示例代码。需要使用pandas和MySQL Connector库。 pythonimport pandas as pd import mysql.connector# 读取Excel文件 df pd.read_excel(data.xlsx)# 连接MySQL数据库 cnx mysql.connector.connect(useryour_username, …

华为无线ac+ap旁挂二层组网常用配置案例

AC控制器理解配置步骤&#xff1a; capwap source interface Vlanif 100 //源IP回包地址 wlan ssid-profile name test //新建个模版名称为test ssid test //wifi名称 wlan security-profile name test //建立安全模版也叫test security wpa-wpa2 psk pass-phrase admin123 a…

linux 安装 cuda

需求&#xff1a; inux 下安装 cuda 进程&#xff1a; 先查看一下系统版本 uname -a查看能支持什么版本的cudacuda toolkit 下载 wget https://developer.download.nvidia.com/compute/cuda/11.1.0/local_installers/cuda_11.1.0_455.23.05_linux.run sudo sh cuda_11.1.0_4…

API 接口是什么?怎么对接 API?

一、API接口是什么&#xff1f; API接口即应用编程接口&#xff0c;是一些预先定义的函数&#xff0c;可以提供应用程序与开发人员基于某软件或硬件以访问一组例程的能力。简单来说&#xff0c;API接口相当于信息的桥梁&#xff0c;它可以让不同平台、应用程序或系统共享数据&…

红帽不再公开RHEL源码,下游如何应对,CentOS的最佳替代需要重新选择

之前写过两篇centos替代的文章 CentOS7将在三年后停止支持&#xff0c;有哪些替代品_centos7 替代_gsls200808的博客-CSDN博客 CentOS8替代盘点_centos8替代品_gsls200808的博客-CSDN博客 现在情况又有新变化了。 当地时间 6 月 21 日&#xff0c;红帽发布公告称&#xff0…

对Element DatePicker时间组件的封装,时间组件开始时间和结束时间绑定

背景 我们时常有时间范围选择&#xff0c;需要选择一个开始时间和一个结束时间给后端&#xff0c;但我们给后端的是两个字段&#xff0c; 分别是开始时间和结束时间&#xff0c;现在使用element绑定的值是一个数组&#xff0c;我们还要来回处理&#xff0c;很麻烦列表也的查询…

JAVA集成国密SM2

JAVA集成国密SM2加解密 一、pom配置二、代码集成2.1、目录结构2.2、源码2.3、测试 三、相关链接 国密算法概述&#xff1a;https://blog.csdn.net/qq_38254635/article/details/131801527 SM2椭圆曲线公钥密码算法 为非对称加密&#xff0c;基于ECC。该算法已公开。由于该算法…

react使用SVGA特效 常用api

下载插件 npm install svgaplayerweb --save react中代码 import React, { useEffect } from react; import SVGA from svgaplayerweb const Svga () > {const bofang () > {var player new SVGA.Player(#demoCanvas);//创建实例var parser new SVGA.Parser(#demo…

centos7安装 mongodb

一、rpm安装 1.1、配置MongoDB Enterprise的yum 源文件 [mongodb-enterprise] nameMongoDB Enterprise Repository baseurlhttps://repo.mongodb.com/yum/redhat/$releasever/mongodb-enterprise/3.4/$basearch/ gpgcheck1 enabled1 gpgkeyhttps://www.mongodb.org/static/pgp…

Pytest使用fixture实现token共享

同学们在做pytest接口自动化时&#xff0c;会遇到一个场景就是不同的测试用例需要有一个登录的前置步骤&#xff0c;登录完成后会获取到token&#xff0c;用于之后的代码中。首先我先演示一个常规的做法。 首先在conftest定义一个login的方法&#xff0c;方法返回token pytes…

【Rust 基础篇】Rust Cargo 自定义构建

导言 在 Rust 中&#xff0c;Cargo 是一个功能强大的构建工具和包管理器&#xff0c;它可以帮助我们管理项目的依赖、构建和发布。Cargo 提供了许多默认的构建行为&#xff0c;但有时我们需要自定义构建过程以满足特定的需求。本篇博客将详细介绍如何在 Rust 中使用 Cargo 自定…

【Maven三】——maven生命周期和插件

系列文章目录 Maven之POM介绍 maven命令上传jar包到nexus 【Maven二】——maven仓库 maven生命周期和插件 系列文章目录前言一、什么是生命周期&why1.三套生命周期2.clean生命周期3.default生命周期4.site生命周期5.命令行与生命周期 二、插件目标三、插件绑定1.内置绑定2…

Matlab使用etopo在线地形数据绘制中国区域DEM地形图

以下是使用MATLAB绘制中国区域DEM地形图的过程和代码示例&#xff1a; 1. 首先&#xff0c;需要从etopo网站下载中国区域的地形数据。进入etopo网站&#xff08;https://www.ngdc.noaa.gov/mgg/global/etopo5.HTML&#xff09;&#xff0c;找到“Download Global Relief Data”…

app爬虫(2)谷歌Nexus6P Frida HOOK 实战

一&#xff0c;环境准备&#xff08;手机有root&#xff09;&#xff1a; PC端&#xff1a;frida16.0.3 pip3 install frida16.0.3PC端&#xff1a;frida-tools12.0.2 pip3 install frida-tools12.0.2手机端&#xff1a;frida-server16.0.2 下载地址&#xff1a;https://gith…

C++ 程序设计:单例+原型(手机原型机和量产机)

1.简介 1.1单例模式 C单例模式被广泛应用于需要全局唯一实例的场景。以下是一些常见的使用场景&#xff1a; 日志记录器 在大多数应用程序中&#xff0c;需要一个全局的日志记录器来记录系统运行时的事件和错误。使用单例模式可以确保只有一个日志记录器实例&#xff0c;并能…

韩老师多目标优化:多目标粒子群算法

一. 内容简介 韩老师多目标优化&#xff1a;多目标粒子群算法 视频: 【2022.2.5韩老师十七课时&#xff08;中&#xff09;多目标优化&#xff1a;多目标粒子群算法】 https://www.bilibili.com/video/BV1eS4y157Xg/?share_sourcecopy_web&vd_source7b377d4a833a67013df5…

libevent:windows环境配置+QT使用

目录 libevent是什么 编译 QT使用 测试代码 libevent是什么 Fast portable non-blocking network programming with Libevent http://www.wangafu.net/~nickm/libevent-book/TOC.html 这篇文档讲的很清楚&#xff0c;尤其是Chapter 1: A tiny introduction to asynchro…

大数据面试基础回答

以下是Hive大数据领域的一些常见问题&#xff1a; 数据倾斜&#xff1a;在Hive中&#xff0c;数据倾斜是一个常见的问题&#xff0c;它会导致查询结果不准确或查询过程异常。为了解决数据倾斜问题&#xff0c;可以尝试以下方法&#xff1a; 使用更高效的数据倾斜处理工具&…