归并排序详解

news2024/12/27 12:48:35

目录

归并排序的核心思想:

递归实现:

非递归实现:

时间复杂度:

空间复杂度:

应用场景:

本文全部以升序为例:

归并排序的核心思想:

先分解在合并:

1.归并的归,指的是回归到最小范围,也就是把要排序的数组,分解成多个小的组合

2.归并的并,就是,先把每个小的组合实现排序,然后把若干个有序的组合合并成一个有序的组合,从而实现排序

如图,举一个简单的例子:

这样就实现了升序

再举个例子:

把10,6,7,1,3,9,4,2排列成升序:

归并排序的一般步骤都是,先分解,在合并。而递归实现和非递归实现,合并的方式都是一样的,只是分解的方法有所差异

所以我们先来把合并的方法讲了先:

如何把两个两个的有序组合,合并成一个有序的组合?

还是举一个简单的例子来讲解:

arr1={1,3} 和arr2={2,4}

如何把这两个有序的数组合并成arr3={1,2,3,4}呢?

思路:

1、定义一个临时数组tmpArr,长度是上面两个小数组元素之和。

2、定义两个指针,c1c2,分别指向arr1和arr2的首元素。

3、比较两个指针所指的元素,把小的那个元素先存放到临时数组中,然后指针往前移动,以此类推,直到arr1和arr2两个数组全部遍历完。

这时候,我们就得到了一个合并后的有序的临时数组tmpArr了。

具体代码及注释细节介绍:

private static void merge(int[] arr,int left,int right,int mid){
 
//arr是我们要排序的整个数组
//[left,right]是一个闭区间,且一定是arr数组中的有效下标
//mid是下标left和right的中间值
//需要合并的数组其实就是:
//[left,mid]以及[mid+1,right]---------》这两个数组都是有序的     

    }
    private static void mergeArr(int[] arr, int left, int right, int mid) {//受到deComposeArr的保护,所有的下标都是有效的

        /*
         * 对两个相邻区间的有序数组合并成一个有序的数组
         * [left,mid]和[mid+1,right]
         * 记住:这两个数组都是已经排好序的了啊啊啊啊啊啊啊啊,先不用问为什么,等看完,回过头来想,就自然懂了*/

        //第一个数组的两个首尾下标
        int head1 = left;
        int end1 = mid;

        //第二个数组的两个首尾下标
        int head2 = mid + 1;
        int end2 = right;//可以不用定义这么多下标,mid和right可以直接用,我这样写只是让大家好理解

        //定义一个临时数组,长度是   *上面两个数组长度之和*
        int[] tmpArr = new int[right - left + 1];//对,要加一,即使一个简单的数学问题

        int k = 0;//临时数组的下标

        while (head1 <= end1 && head2 <= end2) {//只要两个数组,没有全部放进临时数组,就一直循环

            //如果arr[head1]比较小,就把arr[head1]先放进临时数组
            if (arr[head1] < arr[head2]) {
                tmpArr[k++]=arr[head1++];//后置加加,存完,就去判断下一个位置的大小关系
            }else{//和if的逻辑一样
                tmpArr[k++]=arr[head2++];
            }
        }
        
/*
*         除了上面的while循环后,实际上还没有完全把tmpArr数组装完,因为
* 条件head1<=end1或者head2<=end2可能只有一个不符合条件,所以,要记得判断
 */        //把剩下的直接放到tmpArr数组即可
        while(head1<=end1){
            tmpArr[k++]=arr[head1++];
        }
        while(head2<=end2){
            tmpArr[k++]=arr[head2++];
        }
        

        for (int i = 0; i <k ; i++) {//实际上k,就是临时数组的长度
            arr[left+i]=tmpArr[i];
        }

    }

下面的递归和非递归都要用到mergeArr方法(合并)!


递归实现:

前文讲到不论是递归还是非递归,实际上不一样的就在,分解这个步骤,所以我们重点看看,如果把一个数组,分成若干个小的数组,然后去合并的。

有了上面的合并方法,其实递归就很好写了代码:

 private static void deComposeArr(int[] arr, int left, int right) {//先分解,在合并
            if(left>=left)return;//检测无效下标,就直接退出

            int mid=(left+right)/2;
        //递归分解两个区间,直到分成一个一个的元素,然后有序合并
        /*
         * 第一个:
         * 【left,mid】*/

        /*
         * 第二个:
         * 【mid+1,right】*/
            deComposeArr(arr,left,mid);
            deComposeArr(arr,mid+1,right);

            //分解合并后,在最后合并即可
            mergeArr(arr,left,right,mid);

    }

归并排序的递归写法_整体代码:

class TestMerge {
    public static void mergeSort(int[] arr) {//为了保持接口的一致性,只传递一个数组,所以定义这个方法去调用deCompose
        deComposeArr(arr,0,arr.length-1);
    }

    private static void deComposeArr(int[] arr, int left, int right) {//先分解,在合并
            if(left>=right)return;//检测无效下标,就直接退出

            int mid=(left+right)/2;
        //递归分解两个区间,直到分成一个一个的元素,然后有序合并
        /*
         * 第一个:
         * 【left,mid】*/

        /*
         * 第二个:
         * 【mid+1,right】*/
            deComposeArr(arr,left,mid);
            deComposeArr(arr,mid+1,right);

            //分解合并后,在最后合并即可
            mergeArr(arr,left,right,mid);

    }

        private static void mergeArr(int[] arr, int left, int right, int mid) {//受到deComposeArr的保护,所有的下标都是有效的

            /*
             * 对两个相邻区间的有序数组合并成一个有序的数组
             * [left,mid]和[mid+1,right]
             * 记住:这两个数组都是已经排好序的了啊啊啊啊啊啊啊啊,先不用问为什么,等看完,回过头来想,就自然懂了*/

            //第一个数组的两个首尾下标
            int head1 = left;
            int end1 = mid;

            //第二个数组的两个首尾下标
            int head2 = mid + 1;
            int end2 = right;//可以不用定义这么多下标,mid和right可以直接用,我这样写只是让大家好理解

            //定义一个临时数组,长度是   *上面两个数组长度之和*
            int[] tmpArr = new int[right - left + 1];//对,要加一,即使一个简单的数学问题

            int k = 0;//临时数组的下标

            while (head1 <= end1 && head2 <= end2) {//只要两个数组,没有全部放进临时数组,就一直循环

                //如果arr[head1]比较小,就把arr[head1]先放进临时数组
                if (arr[head1] < arr[head2]) {
                    tmpArr[k++]=arr[head1++];//后置加加,存完,就去判断下一个位置的大小关系
                }else{//和if的逻辑一样
                    tmpArr[k++]=arr[head2++];
                }
            }

    /*
    *         除了上面的while循环后,实际上还没有完全把tmpArr数组装完,因为
    * 条件head1<=end1或者head2<=end2可能只有一个不符合条件,所以,要记得判断
     */        //把剩下的直接放到tmpArr数组即可
            while(head1<=end1){
                tmpArr[k++]=arr[head1++];
            }
            while(head2<=end2){
                tmpArr[k++]=arr[head2++];
            }


            for (int i = 0; i <k ; i++) {//实际上k,就是临时数组的长度
                arr[left+i]=tmpArr[i];
            }

        }
}

非递归实现:

其实分解思路都一样,就是把数组先分解成一个一个有序的数字,然后两个两个合并,然后四个四个合并........

直到全部合并完。

 public static void deComposeNoRecursion(int[] arr){
        int gap=1;//一个一个合并开始,一个gap的区间相当于一个要合并的数组

        while(gap<arr.length) {//需要合并的数组,长度没有到达整个要排序数组的长度,就一直分解,合并
            for (int i = 0; i < arr.length; i = i + gap * 2) {//因为不再是递归,所以循环退出条件是数组的长度
                //把两个区间(i到i+gap和i+gap+1到i+gap+gap)合并完,再去到另一个区间合并,所以i=i+gap*2

                //定义: 两个要合并的数组,下标区间是[left,right]
                //我们只要不断地去分割区间(每次循环完,区间倍增),然后合并,即可实现排序
                int left = i;

                int right = left + gap * 2 - 1;//按照直觉好似是left+gap*2,但是实际上还要-1才行,因为left已经算是一个了
                int mid=(right+left)/2;//两个下标的中间值

                //注意按照for循环走,right和mid是有可能越界的,想要解决这个问题也很简单:
                if(mid>=arr.length)mid=arr.length-1;
                if(right>=arr.length)right=arr.length-1;//直接赋值最后一个元素即可,等一下也会自动合并这个数组

                mergeArr(arr,left,right,mid);//合并

            }
            gap*=2;//for循环结束,说明两个gap区间的数已经有序了,所以两个gap区间的数,可以看成一个区间了
        }

    }

时间复杂度:

还是使用刚才的图:

先看分解的时间复杂度:

可以把他想象成一颗二叉树,分解的次数实际上就是二叉树的高度,既logN

再看合并的时间复杂度:

实际上每次的合并都需要对整个数组进行一次遍历(这也是为什么归并排序稳定的原因),因此是N

所以归并排序的时间复杂度为:O(N*logN)

空间复杂度:

还是上面的图,空间复杂度,不能像时间复杂度那样累加计算。

因为每一次调用函数都会定义一个tmp数组,函数结束tmp数组内存就会销毁

所以我们只用考虑tmp数组,取最大的情况即可,也就是N。

因此归并排序的时间复杂度为:O(N)

应用场景:

归并排序通常会用到海量数据的排序问题

至于为什么,我们先来了解一下一些前置知识就懂了:

外部排序:排序过程需要在磁盘等外部内存进行的排序

为什么会有外部排序?

加入要排序的数据大小是100G

但是电脑内存只有1G,另外的99G就无法实现排序了

这时归并排序就派上了用场:

因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序

1. 先把文件切分成 200 份,每个 512 M

2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以

3. 进行 2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

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

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

相关文章

运算符重载(2)

1.赋值运算符重载 #include<iostream> using namespace std;class Person { friend void test01(); public:Person(int age){m_Age new int(age);}/*堆区的数据由程序员手动开辟并手动释放*/~Person(){if (m_Age ! NULL){delete m_Age;}}Person& operator(Person &a…

Python_AI库 Matplotlib的应用简例:绘制与保存折线图

本文默认读者已具备以下技能&#xff1a; 熟悉Python基础语法&#xff0c;以自行阅读python代码块熟悉Vscode或其它编辑工具的应用 在数据可视化领域&#xff0c;Matplotlib无疑是一个强大的工具。它允许我们创建各种静态、动态、交互式的可视化图形&#xff0c;帮助我们更好…

币圈资讯Cryptosquare论坛

在加密货币世界中&#xff0c;信息的及时获取对于投资者和交易者至关重要。今天&#xff0c;我将向大家介绍Cryptosquare这个综合性资讯论坛&#xff0c;它汇集了币圈新闻、空投信息、社会热点以及与Web3相关的工作信息。让我们一起解锁加密世界的种种可能性&#xff0c;探索Cr…

鹏哥C语言复习——字符函数与字符串函数

目录 一.字符函数 1.字符分类函数 2.字符转换函数 二.基础字符串函数 1.strlen函数 2.strcpy函数 3.strcat函数 4.strcmp函数 三.基础字符串函数优化 1.strncpy函数 2.strncat函数 3.strncmp函数 四.进阶字符串函数 1.strstr函数 2.strtok函数 3.strerror函数 一…

Eclipse 如何导入一个 Maven 项目

如果你的项目是 Maven 项目的话&#xff0c;导入的时候需要使用 Import&#xff0c;而不能使用打开项目的方式。 选择导入 选择导入 Maven 项目 然后选择 Maven 项目&#xff0c;开始导入。 选择目录后导入 然后选择你需要导入的目录后&#xff0c;单击导入。 Eclipse 如何导…

Llama-7b-Chinese本地推理

Llama-7b-Chinese 本地推理 基础环境信息&#xff08;wsl2安装Ubuntu22.04 miniconda&#xff09; 使用miniconda搭建环境 (base) :~$ conda create --name Llama-7b-Chinese python3.10 Channels:- defaults Platform: linux-64 Collecting package metadata (repodata.js…

【MySQL精炼宝库】数据库的约束 | 表的设计 | 聚合查询 | 联合查询

目录 一、数据库约束 1.1 约束类型&#xff1a; 1.2 案例演示&#xff1a; 二、表的设计 2.1 一对一: 2.2 一对多: 2.3 多对多: 2.4 内容小结&#xff1a; 三、新增 四、查询 4.1 聚合查询&#xff1a; 4.1.1 聚合函数&#xff1a; 4.1.2 GROUP BY子句&#xff1a…

nginx配置ip_hash负载均衡策略

一、nginx配置ip_hash负载均衡策略 nginx默认的负载均衡策略为轮询&#xff0c;某些场景需要使用ip_hash负载策略&#xff0c;即&#xff1a;同一个ip地址&#xff0c;永远访问nginx后面同一台tomcat。配置示例如下&#xff0c;主要是设置ip_hash&#xff1a; upstream www.ab…

B站美化插件,支持自定义,太酷辣~

大公司的软件和网站通常具有优雅的默认界面设计。 以国内二次元聚集地B站为例&#xff0c;可以说它的UI设计非常吸引人。与其他视频网站繁复的设计相比&#xff0c;B站的界面设计可以说是遥遥领先 然而&#xff0c;总有些人对默认的用户界面感到不满意&#xff0c;他们渴望尝试…

Arm功耗管理精讲与实战

安全之安全(security)博客目录导读 思考 1、为什么要功耗管理&#xff1f;SOC架构中功耗管理示例&#xff1f;功耗管理挑战&#xff1f; 2、从单核->多核->big.LITTLE->DynamIQ&#xff0c;功耗管理架构演进? 3、什么是电压域&#xff1f;什么是电源域&#xff1f…

C#上位机与S7-200Smart通信注意事项

S7-200SMART连接 问题描述 我们使用C#开发上位机和S7-200Smart系列PLC交互数据时&#xff0c;大多会用到Sharp7、Snap7之类的通信类库。有些通信类库默认的使用的是PG连接资源&#xff0c;而对于S7-200Smart来说&#xff0c;它的PG连接资源只有1个。 官网200smart提到的连接数…

smart200 做client,modbus_tcp读取modbus_slave

这里还隐藏一个重要的设置&#xff0c;就是站地址。这个在库函数里。不同plc位置会不一样&#xff0c;我这里是vb1651对应modbus的地址为255&#xff0c;这个值我们可以自己更改&#xff0c;范围为1-247. 打开modbus_slave 软件&#xff0c;

MySQL recursive 递归

MySQL 从最内的select开始执行&#xff0c;但是同一个select clause可以在查询的结果上继续查询。 SELECT menu_id,parent_id,(SELECT m1.parent_id FROM sys_menu AS m1 WHERE m1.menu_idm.parent_id) FROM sys_menu AS m WHERE m.menu_id 89 方案1.通过recursive递归 使用…

Matlab 使用subplot绘制多个子图,一元拟合

实现效果&#xff1a; clc; clear;filename sri.xlsx; % 确认文件路径data readtable(filename); datavalue data{:,2:end}; datavalue datavalue;fig figure(Position, [0, 0, 1500, 900]); indexString ["(a)","(b)","(c)","(d)&qu…

科技感十足特效源码

源码介绍 科技感十足特效源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面 源码截图 源码下载 科技感十足特效源码

十大常见B端管理系统,经常用,未必能叫上名字。

常见的B端管理系统有以下几种&#xff1a; 客户关系管理系统&#xff08;CRM&#xff09;&#xff1a; CRM系统帮助企业管理与客户相关的信息&#xff0c;包括客户联系信息、销售机会、市场活动等。它提供了客户数据整合、销售流程管理、客户沟通跟进等功能&#xff0c;帮助企…

【人工智能基础】逻辑回归实验分析

实验环境&#xff1a;anaconda、jutpyter Notebook 实验使用的库&#xff1a;numpy、matplotlib 一、逻辑回归 逻辑回归是一个常用于二分类的分类模型。本质是&#xff1a;假设数据服从这个分布&#xff0c;然后使用极大似然估计做参数的估计。 二、实验准备 引入库、预设值…

【三】Spring Cloud Ribbon 实战

Spring Cloud Ribbon 实战 概述 一直在构思写一个spring cloud系列文章&#xff0c;一方面是对自己实践经验进行一次完整的梳理&#xff0c;另一方面也是希望能够给初学者一些借鉴&#xff0c;让初学者少走些弯路&#xff0c;看到本系列博客就能够很好的把微服务系列组件用好。…

字符函数·字符串函数·C语言内存函数—使用和模拟实现

字符函数字符串函数C语言内存函数 1.字符分类函数2. 字符转换函数3. strlen的使用和模拟实现4.strcpy的使用和模拟实现5.strcat的使用和模拟实现6.strcmp的使用和模拟实现7.strncpy的模拟和实现8.strncat的实现和模拟实现9.strncmp函数使用10.strstr的使用和模拟实现11.strtok函…

【python技术】akshare爬取A股最新业绩预告保存进excel的简单示例

最近A股上市公司陆续在出年报和一季度报了&#xff0c; 心里寻思着要不用python把这些数据爬取下来分析下&#xff0c;说干就干。 数据来源网站东方财富&#xff1a;https://data.eastmoney.com/bbsj/ 我这个人比较懒&#xff0c;直接用akshare封装的方法来搞定 之前用aksha…