<数据结构>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,一经查实,立即删除!

相关文章

华为无线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;它可以让不同平台、应用程序或系统共享数据&…

对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…

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

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

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…

matlab入门

命名规则&#xff1a; clc&#xff1a;清除命令行的所有命令 clear all&#xff1a;清除所有工作区的内容 注释&#xff1a;两个% 空格 %% matlab的数据类型 1、数字 3 3 * 5 3 / 5 3 5 3 - 52、字符与字符串 s a %% 求s的ascill码 abs(s) char(97) num2str(65) str I…

家政小程序开发-H5+小程序

移动互联网的发展&#xff0c;微信小程序逐渐成为商家拓展线上业务的重要手段。家政服务作为日常生活中不可或缺的一部分&#xff0c;也开始尝试通过小程序来提高服务质量和效率。 下面是一篇关于家政小程序开发的H5小程序的文章&#xff0c;希望对您有所帮助。 家政服…

Redis进阶底层原理- 缓冲区

Redis中使用了很多缓冲区&#xff0c;在redis各个环节起到了非常核心的作用。下面来一一介绍一下&#xff1a; 输入输出缓冲区&#xff08;客户端缓冲区&#xff09; Redis中的输入输出缓冲区是为了平衡客户端发送命令和服务端处理命令的速度差异&#xff0c;如果客户端发送指…

一本通1910:【00NOIP普及组】计算器的改良题解

今天是编程集训的第二天&#xff0c;也是我来到CSDN整整1年。感谢所有阅读过我的文章的人&#xff0c;谢谢。 今天的比赛难度略低于昨天&#xff0c;但这道题也卡了我好久。 进入正题 题目&#xff1a; 题目描述&#xff1a; NCL是一家专门从事计算器改良与升级的实验室&a…

手把手带你实现ChatGLM2-6B的P-Tuning微调

参考文献&#xff1a;chatglm2ptuning 注意问题1&#xff1a;AttributeError: ‘Seq2SeqTrainer’ object has no attribute is_deepspeed_enabl torch.distributed.elastic.multiprocessing.errors.ChildFailedError: 可能是版本太高&#xff0c;可以参考chatglm2的环境

mysql笔记

目录 1、root用户密码忘记 2、SQL的分类 2.1、DQL数据查询语言 前言 2.1.1、设置别名 2.1.2、去除重复行 2.1.3、空值参与运算 2.1.4、着重号 2.1.5、显示表结构 2.1.6、算数运算符 2.1.7、比较运算符 2.1.8、逻辑运算符 2.1.9、位运算符 2.1.10、 模糊查询 2.1.…

基于Java+SpringBoot+Vue前后端分离校园管理系统详细设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

sqli-labs 堆叠注入 解析

打开网页首先判断闭合类型 说明为双引号闭合 我们可以使用单引号将其报错 先尝试判断回显位 可以看见输出回显位为2&#xff0c;3 尝试暴库爆表 这时候进行尝试堆叠注入&#xff0c;创造一张新表 ?id-1 union select 1,database(),group_concat(table_name) from informatio…

分布式应用之zookeeper集群+消息队列Kafka

一、zookeeper集群的相关知识 1.zookeeper的概念 ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Google的Chubby一个开源的实现&#xff0c;是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件&#xff0c;提供的功能…

一级分类全覆盖!安全狗入选《嘶吼2023网络安全产业图谱》

7月10日&#xff0c;嘶吼安全产业研究院联合国家网络安全产业园区&#xff08;通州园&#xff09;正式发布《嘶吼2023网络安全产业图谱》。作为国内云原生安全领导厂商&#xff0c;安全狗入选图谱中的多个细分领域。 据悉&#xff0c;本次《嘶吼2023网络安全产业图谱》采用了市…

JavaScript——基础知识及使用

初识 JavaScript JavaScript (简称 JS) 是世界上最流行的编程语言之一.一个脚本语言, 通过解释器运行.主要在客户端(浏览器)上运行, 现在也可以基于 node.js 在服务器端运行. JavaScript 的能做的事情: 网页开发(更复杂的特效和用户交互)网页游戏开发服务器开发(node.js)桌…