【排序】归并排序

归并排序

  • 动图演示:

在这里插入图片描述

  • 基本思想:分治思想

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

假设我们有左右两块有序区间的数组,可以对它直接进行合并。此时我们需要借助第三方数组,一次比较两块区间的起始位置,把小的那个放到新数组,随后依次比较,小的就放新数组,一直到结束。

但是现在存在一个问题,上述条件是假设了左半区间和右半区间有序,但是原先数组是无序的,也就是左半区间和右半区间均无序。怎么才能达到左半区间和右半区间有序最后再归并成整体有序呢?这就体现到了分治的思想了,将数组一直分,分到1个1个的,归并成有序变成2个2个的,然后归并成有序成4个4个的,最后再4个4个的归并成有序,最终至整体有序。

  • 画图解析其完整的归并过程:

在这里插入图片描述

这里我们先用代码实现其分解递归的过程,并用打印法表示其结果:

在这里插入图片描述

画图演示其部分递归分治的过程:

在这里插入图片描述

  • 总代码如下:
void _MergeSort(int* a, int begin, int end, int* tmp)
{if (begin >= end)return; //区间不存在就返回int mid = (begin + end) / 2;//[begin, mid] [mid+1, end]_MergeSort(a, begin, mid, tmp); //递归左半_MergeSort(a, mid + 1, end, tmp); //递归右半//归并[begin, mid] [mid+1, end]//printf("归并[%d,%d][%d,%d]\n", begin, mid, mid + 1, end);int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int index = begin;while (begin1 <= end1 && begin2 <= end2){//将较小的值放到tmp数组里头if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}//如若begin2先走完,把begin1后面的元素拷贝到新数组while (begin1 <= end1){tmp[index++] = a[begin1++];}//如若begin1先走完,把begin2后面的元素拷贝到新数组while (begin2 <= end2){tmp[index++] = a[begin2++];}//归并结束后,把tmp数组拷贝到原数组memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}//归并排序
void MergeSort(int* a, int n)
{//malloc一块新数组int* tmp = (int*)malloc(sizeof(int) * n);assert(tmp);_MergeSort(a, 0, n - 1, tmp);free(tmp);
}

归并排序非递归

  • 思想:

归并的非递归不需要借助栈,直接使用循环即可。递归版中我们是对数组进行划分成最小单位,这里非递归我们直接把它看成最小单位进行归并。我们可以通过控制间距gap来完成,先看图:

在这里插入图片描述

上述情况其实是在理想状态下可行的,只要数组长度不是2的次方倍都会出现问题,先简要看下理想状态下的伪代码,并用printf打印下归并过程:

在这里插入图片描述

再强调一遍,只要数组长度不是2的次方倍都会出现问题,像上述长度为8没有问题,那如若长度为6呢?

在这里插入图片描述

当长度为6不再是2的次方数时就运行出现问题了,综上我们需要考虑下极端情况:根据上述的区间范围,我们可以总结出以下三个可能会出现越界的情况:

  1. end1越界。
  2. begin2越界。
  3. end2越界。

1、end2越界:

在这里插入图片描述

2、begin2和end2均越界:

在这里插入图片描述

3、end1和begin2和end2均越界 :

在这里插入图片描述

综上,我们需要单独对这些极端情况处理。

//end1越界,修正即可
if (end1 >= n)
{end1 = n - 1;
}
//begin2越界,第二个区间不存在
if (begin2 >= n)
{begin2 = n;end2 = n - 1;
}
//begin2 ok,end2越界,修正下end2即可
if (begin2 < n && end2 >= n)
{end2 = n - 1;
}
  • 总代码如下:
//归并非递归
void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);assert(tmp);int gap = 1;while (gap < n){//分组归并,间距为gap是一组,两两归并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;//end1越界,修正即可if (end1 >= n){end1 = n - 1;}//begin2越界,第二个区间不存在if (begin2 >= n){begin2 = n;end2 = n - 1;}//begin2 ok,end2越界,修正下end2即可if (begin2 < n && end2 >= n){end2 = n - 1;}printf("归并[%d,%d][%d,%d]\n", begin1, end1, begin2, end2);int index = i;while (begin1 <= end1 && begin2 <= end2){//将较小的值放到tmp数组里头if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}//如若begin2先走完,把begin1后面的元素拷贝到新数组while (begin1 <= end1){tmp[index++] = a[begin1++];}//如若begin1先走完,把begin2后面的元素拷贝到新数组while (begin2 <= end2){tmp[index++] = a[begin2++];}}memcpy(a, tmp, n * sizeof(int));gap *= 2;}free(tmp);
}

归并排序特性总结

1、归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

2、时间复杂度:O(N*logN)。

3、空间复杂度:O(N)。

4、稳定性:稳定 。

内排序和外排序

在排序中,分为内排序和外排序,简单了解下其概念:

  • 内排序:数据量较少,在内存中进行排序。
  • 外排序:数据量很大,在磁盘上进行排序。

而我们前面学习的排序中,归并排序既可作为内排序,也可作为外排序,而其它几个排序只能是内排序,这也就说明了在处理数据量很大时,采用归并排序才能解决,其它排序不可。

如若我要排10亿个整数,就只能使用归并排序了,现在来简要算下其占比大小:

  • 1G = 1024MB
  • 1MB = 1024KB
  • 1KB = 1024Byte
  • 综上1G = 102410241024Byte,而10亿个整数40亿Byte,所以10亿个整数占4G

现在有10亿个整数(4G)的文件,只给你1G的运行内存,请对文件中的10亿个数进行排序。

核心思想: 数据量大,加载不到内存。想办法控制两个有序文件,两个有序文件归并成一个更大的有序文件。可以把这4G的文件分成4等份,每一份1G,分别读到内存进行归并排序,排完后再写回到磁盘小文件。

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

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

相关文章

深入理解C语言(4):文件操作详解

文章主题&#xff1a;文件操作详解&#x1f30f;所属专栏&#xff1a;深入理解C语言&#x1f4d4;作者简介&#xff1a;更新有关深入理解C语言知识的博主一枚&#xff0c;记录分享自己对C语言的深入解读。&#x1f606;个人主页&#xff1a;[₽]的个人主页&#x1f3c4;&#x…

Linux中alarm/setitimer函数(信号函数)

alarm函数 函数原型&#xff1a; unsigned int alarm(unsigned int seconds); 函数描述&#xff1a;设置定时器&#xff08;闹钟&#xff09;。在指定seconds后&#xff0c;内核会给当前进程发送 14&#xff09;SIGALRM信号。进程收到该信号&#xff0c;默认动作终止。每个进程…

JAVA-多进程开发-创建等待进程

前言 在项目中&#xff0c;为了实现“并发编程”&#xff08;同时执行多个任务&#xff09;&#xff0c;就引入了“多进程编程”&#xff0c;把一个很大的任务&#xff0c;拆分成若干个很小的任务&#xff0c;创建多个进程&#xff0c;每个进程分别负责其中的一部分任务。 这也…

【数据结构】计算节点个数和二叉树高度(C语言版)

数据结构——计算节点个数、二叉树高度 一、计算各种节点 &#xff08;1&#xff09;计算总节点&#xff1a;&#xff08;2&#xff09;计算单分支节点&#xff1a;&#xff08;3&#xff09;计算双分支节点&#xff1a; 二、计算二叉树高度 代码实现&#xff1a; 一、计算各种…

NSSCTF Round#18 RE GenshinWishSimulator WP

恶搞原神抽卡模拟器 看到软件的界面&#xff0c;大致有三种思路&#xff1a; 修改石头数量一直抽&#xff0c;如果概率正常肯定能抽到&#xff08;但是估计设置的概率是0&#xff09;在源码里找flag的数据把抽卡概率改成100%直接抽出来 Unity逆向&#xff0c;根据经验应该dnsp…

MySQL数据库⑩_视图+MySQL用户管理(增删查改)

目录 1. 视图的概念和规则限制 2. 视图的基本使用 2.1 创建视图 2.2 修改视图影响基表 2.3 修改基表影响视图 2.4 删除视图 3. MySQL用户管理 3.1 用户信息 3.2 创建用户 3.3 修改用户密码 3.4 删除用户 4. 用户权限 4.1 MySQL权限 4.2 给用户授权 4.3 回收权限…

Vue-自定义属性和插槽(五)

目录 自定义指令 基本语法 (全局&局部注册) 指令的值 练习&#xff1a;v-loading 指令封装 总结&#xff1a; 插槽&#xff08;slot&#xff09; 默认插槽 插槽 - 后备内容&#xff08;默认值&#xff09; 具名插槽 具名插槽基本语法: 具名插槽简化语法: 作…

单片机学习路线(简单介绍)

学习单片机对于电子爱好者和未来的嵌入式系统工程师来说是一段激动人心的旅程。单片机因其强大的功能、灵活性以及在各种智能设备中的广泛应用&#xff0c;成为了电子和计算机科学领域一个不可或缺的组成部分。如果你对如何开始这段旅程感到好奇&#xff0c;那么你来对地方了。…

SpringIOC之support模块ReloadableResourceBundleMessageSource

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

【MySQL】待修改

外键约束 含义 外键&#xff1a;用来让两张表的数据之间建立连接&#xff0c;从而保证数据的完整性和一致性。 员工表emp&#xff08;子表&#xff09; idnameagejobsalaryentrydatemanageriddept_id1金庸66总裁200002000-01-01null52张无忌20项目经理125002005-12-05113杨…

【安装指南】图床神器之Picgo下载、安装详细教程

&#x1f33c;一、概述 PicGo是一款开源的图片上传、管理工具&#xff0c;旨在帮助用户快速上传图片到云存储或图床&#xff0c;并提供链接方便在网页或其他应用中使用。它支持各种常见的图床服务商&#xff0c;如GitHub、七牛云、腾讯云等&#xff0c;并提供了简洁易用的界面和…

python3 中try 异常调试 raise 异常抛出

一、什么是异常&#xff1f; 异常即是一个事件&#xff0c;该事件会在程序执行过程中发生&#xff0c;影响了程序的正常执行。 一般情况下&#xff0c;在Python无法正常处理程序时就会发生一个异常。 异常是Python对象&#xff0c;表示一个错误。 当Python脚本发生异常时我…