基本排序算法 之六 ——归并排序

C++排序算法:归并排序详解 阅读详情

 


基本排序算法总结与对比 之六  ——归并排序 


1、归并排序  递归版本

template<typename T>
void mergeSort(T arr[], int lo, int hi)
{
    if(hi - lo < 2) return;
    int mi = (lo + hi) >> 1;
    mergeSort(arr, lo, mi);  mergeSort(arr, mi, hi);  merge(arr, lo, mi, hi);
}

template<typename T>
void merge(T arr[], int lo, int mi, int hi)
{
    T* C = arr + lo;    //C 为待排序数列的起点

    T* A = new T[mi - lo];    
    for(int i = 0; i < mi - lo; A[i] = C[i++]);    //拷贝arr[]中区间[lo,mi)的元素到A

    T* B = arr + mi;    //arr[]中区间[mi,hi)的元素不用单独拷贝出来,排序过程中后半段元素是安全的

    for(int a = 0, b = 0, c = 0; a < mi - lo || b < hi - mi; )    //按升序排列
    {
        if(a < mi - lo && (!(b < hi - mi) || A[a] <= B[b])) C[c++] = A[a++];
        if(b < hi - mi && (!(a < mi - lo) || A[a] >  B[b])) C[c++] = B[b++];
    }
    delete [] A;
}

       不难得出,上述代码中归并排序的时间复杂度最好,最坏,平均均为O(nlogn)。(证明过程参考有关书籍)

       实际上略微修改上段代码,即可提高算法效率: 

       ① 频繁的new和delete时间成本极高,可以在函数运行初分配好空间。

       ② merge(arr, lo, mi, hi)仅仅需要在arr[mid - 1] > arr[mid]才有必要执行,若arr[mid - 1] ≤ arr[mid]则其本身就是有序的。

       ③ 归并函数中引入了较为复杂的控制逻辑,目的是为了统一对不同情况的处理。尽管如此可使代码在形式上更为简洁,但同时也会在一定程度上造成运行效率的下降。

       基于以上几点,对上算法略做改进。

 2、归并排序 递归版本 的改进A

template<typename T>
void mergeSort(T arr[], int lo, int hi, bool init = true, T* A = nullptr, int len = 0)
{
    if (hi - lo < 2) { return; }
    if (init) { A = new T[hi - lo]; len = hi - lo; }  //先准备好存储[lo,mi)数据的空间A
                                                      //并且记录下数组长度len
    int mi = (lo + hi) >> 1;
    mergeSort(arr, lo, mi, false, A, len);    mergeSort(arr, mi, hi, false, A, len);
    if (arr[mi - 1] > arr[mi]) merge(arr, lo, mi, hi, A, len); //仅在arr[mid - 1] >         
                                                               //arr[mid]时才执行归并
}

template<typename T>
void merge(T arr[], int lo, int mi, int hi, T* A, int len)
{
    T* C = arr + lo;
    for (int i = 0; i < mi - lo; A[i] = C[i++]);
    T* B = arr + mi;

    //归并过程中可能会出现4种情况
    //1、A,B中数的均没有放完(放进C,我们先这么认为 因为B与C的后半段是重叠的)
    //2、A没有放完,B放完了
    //3、A放完了,B没有放完
    //4、A,B均放完了
    //情况1:a < mi - lo && b < hi - mi,需要对A,B中的数比较
    //情况2:a < mi - lo && b >= hi - mi,只需要把A中剩余的数拷贝到C后边即可
    //情况3:a > mi - lo && b < hi - mi,只需要把B中剩余的数放到C后边即可,
           //但是B与C的后半段是重叠的,即B中剩余的元素就是C的后边部分,所以不用任何操作!
    //情况4:均放完了,也不用任何操作
    //--综上:需要操作的情形只有情况1和2,也就是a < mi - lo
    //--然后只需要 b < hi - mi时比较元素 和 b >= hi - mi时拷贝元素
    //--因此,归并的逻辑控制可以简化如下:

    for (int a = 0, b = 0, c = 0; a < mi - lo; )  //只有a < mi - lo时才需要操作
    {
        if (b < hi - mi){                         //b < hi - mi时比较元素
            if(A[a] <= B[b])  C[c++] = A[a++];
            else C[c++] = B[b++];
        }
        else                                      //b >= hi - mi时拷贝元素
            C[c++] = A[a++];
    }
    if (len == hi - lo) delete[] A; 
}

        上述函数的参数列表可能显得有些冗余,但为了统一函数入口,使其简单易用,而不得不做出一些牺牲。 实际测试表明,改进的归并排序A 在对大量数据排序时 所耗时间 为 未改进的排序方法 的一半左右。

        但是,以上改进仍然有不足之处: 

        ① 递归方法不是尾递归形式,最高栈高为log2n,虽然不至于栈满,但空间利用还可以压榨。                                                        

3、归并排序 迭代版本

        若递归函数的尾递归形式难以直接按逻辑列出,则先考虑迭代形式。或者仍然按照普通的递归逻辑,建立一个栈 来储存参数 以实现尾递归。本文将不讨论尾递归形式的实现方法,网络上已经有一个尾递归实现的版本可以参考。

        下面列出归并排序的迭代形式。

        迭代版本的实质上是省略了递归版本的递归分解部分,从而直接进行归并。

                                       

        先对 【 n-n 2n元素归并】 做个解释:第一路n个有序元素 与 第二路n个有序元素 归并成 共2n个有序元素 的 归并操作。

        从1-1 2元素归并 → 2-2 4元素归并 → 4-4 8元素归并 →。。。 → n-n 2n元素归并。(代码中为:step-_step  step+_step元素归并  ,_step为 step 或者 hi - _lo - step 。)

        但是,在上述迭代过程中存在2种特殊情况:

         ① 在 k-k 2k元素归并时,可能 第二路 元素不足k个(因为在末尾部分),即实际应该为k-k1 k+k1元素归并(step-_step  step+_step元素归并)。

         ② 在 k-k 2k元素归并时,可能 剩余元素连 第一路 都不足,即末尾剩余的不足k个(或者刚好k个)元素 不能进行归并。

        对于情况①:仅需要把第二路元素的长度设置为 排除 第一路元素后的 剩余的 元素个数 即可。

        对于情况②:对于出现剩余元素连 第一路 都不足,那么剩余元素肯定是有序的(证明略)。因此不用考虑对剩余元素的归并,因为归并长度是2倍递增的,这些剩余元素最终会称为情况①中的第二路元素。

         基于以上分析,实现代码如下:

template<typename T>
void mergeSort(T arr[], int lo, int hi)
{
    T *A = new T[hi-lo], *B, *C;
    int _step = 1;  //储存第二路元素的长度用, 提前分配空间 避免循环中反复分配, 要是开心,后边的变量都可以提前分配好空间
    for(int step = 1; step < hi - lo; step <<= 1)  //step为第一路元素长度
    {
        for(int _lo = lo; _lo < hi - step; _lo += (step << 1))  //_lo < hi - step 为考虑情况 2
        {
            _step = _lo + step << 1 > hi ? hi - _lo - step : step;  //考虑情况 1,设置第二路元素长度
            C = arr + _lo;   B = C + step; 
            for(int i = 0; i < step; A[i] = C[i++]);  //拷贝第一路元素到A
            
            //第一路元素[_lo,_lo + step)与第二路元素[_lo + step,_lo + step +_step) 进行step-_step step+_step元素归并
            for(int a = 0,b = 0, c = 0; a < step; )
            {
                if(b < _step)
                {
                    if(A[a] <= B[b]) C[a++] = A[a++];
                    else C[c++] = B[b++];
                }
                else
                    C[a++] = A[a++];
            }
        }
    }
    delete [] A;
}

       上述代码没有考虑 只有当 arr[mid - 1]  > arr[mid] (即C[step - 1] > B[0])时才需要进行归并 这一条件,逻辑上似乎设置arr[mid - 1] > arr[mid] 的条件判断 能够避免不需要的归并操作。但是实际的随机数排序测试中加入了这一条件判断反而耗时略多。(笔者的猜测是:可能出现arr[mid - 1]  <= arr[mid] 从而不需要进行归并操作 的概率太低了,节省的时间反而不能补偿循环中进行arr[mid - 1]  > arr[mid] 条件判断的时间!)所以,代码中没有加入这一考虑。 当然,这样在最好的情况下就会更耗时!

IDEA 2023.2最新破解安装教程(含详细配置步骤及常见问题解决) 本文详细介绍了IDEA 2023.2的安装流程及高效开发环境搭建指南,涵盖基础配置、项目结构优化、核心功能使用、插件推荐、性能优化等全方位内容,帮助开发者快速掌握这一强大的Java开发工具。 阅读详情

相关推荐

【安装文档】TRex流量分析仪保姆级安装指南--基于VMware虚拟机(ubantu18.04@Intel 82545EM

如果之前添加了网卡,这里就可以看见有3个网卡(没添加的现在再加也行),* Active *为Linux内核正在使用的网卡,另外两张是未启用的网卡(注:未启用的网卡在终端中使用ifconfig命令是查不到的,需要先使用ip addr找出网卡,再激活,此时网卡会变为* Active *状态。

lingshengxiyou的博客 2130

【数据结构】笔记(3)——算法分析

分析的原则——去粗取精 He caculated just like men breathe, as eagles sustain themselves in the air. Francois Arago 复杂度分析之级数求和分析算法的单调性和确定性,是分析算法是否可行的重要方法举例——求循环的复杂度 举例——封底估计 估计地球的半径: 举例——算法复杂度对于计算的影

_TFBoy的小茶馆 489

Qi标准下的充电协议及其记忆技巧

记忆无线充电协议中的。

m0_73991989的博客 1320

归并排序-CPP实现

**交换顺序原因:** 当C全部填满A,k = lc,如果不交换顺序, B向A中填充一个元素,此时j++,如果j++此时等于lb,那么数组B就会越界访问,但是如果交换顺序就是先访问数组B,不会造成数组越界

CODER-GODV的博客 395

MRI多序列,多中心数据集预处理--使用FSL-Flirt 配准数据

FLIRT(FMRIB 的线性图像配准工具)是用于线性(仿射)模态内和模态间脑图像配准的全自动稳健且准确的工具。 什么是配准 在这两张图片上,很容易看出它们没有对齐,左边相对右边倾斜,虽然我们人类视觉系统还是能把对应的解剖关系连系在一起,但是我们使用计算机分析时确不能够 因此,我们希望相同的解剖结构对齐,用坐标表示的话,希望他们在相同的坐标上。如下图 对齐这些解剖结构的过程就叫做配准(register) 因此,配准的目的也是使相同的解剖结构在同一个位置,便于分析。可以将CT和MRI配准,看同一结构在两个

Tina姐的博客 1万+

保姆级教程:IAR for STM8 3.11版本安装与破解全流程(附资源包)

本文提供IAR for STM8 3.11版本的详细安装与破解教程,涵盖环境准备、主程序安装、许可配置及实战验证全流程。针对新手常见问题,如许可证报错、驱动安装失败等,给出了具体解决方案,帮助开发者快速搭建稳定的STM8开发环境。

study的专栏 952

C++数据结构学习笔记(三)

Back-of-The-Envelope Calculation 封底估算,用于复杂度的分析 减而治之 sum(int A[],int n) { return (n<1)? 0: sum(A,n-1)+A[n-1]; } T(n)=T(n-1)+O(1) T(0)=O(1) 故T(n)=O(n) 数组倒置 void reverse(int * A,int lo,

zjx0126的博客 335

华为OD机考双机位A卷 - 开心消消乐(Java & Python& JS & C/C++ & GO )

华为OD机考双机位A卷 - 开心消消乐(OD上机考试双机位A卷)。提供在线OJ刷题。Java、C++、Python、GO语言、C语言、JavaScript题解,并提供完整思路讲解。

算法大师! 1268

图解归并排序,带你彻底了解清楚!

写在前面: 大家好,我是时光。 今天给大家带来的是排序算法中的归并排序,这种排序需要拆分归并。 我采用图解方式讲解,争取写透彻。话不多说,开始! 思维导图: 归并排序思维导图 1,归并排序的概念 1.1,算法介绍 和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

程序员的时光 2万+

特殊通信领域场景应用:基于Simulink的数字预失真(DPD)算法仿真建模示例

增益压缩:高功率输入时输出信号的增益下降。相位失真:输入信号的相位随功率变化而改变。带外干扰:非线性失真会引入额外的频谱分量,影响邻道信号。多项式模型:用多项式拟合功率放大器的非线性特性。查找表(LUT):通过离线训练生成预失真映射表。自适应算法:实时调整预失真参数以动态补偿失真。发送端:生成随机比特流并调制为射频信号。预失真模块:对输入信号进行预失真处理。功率放大器模块:模拟非线性失真。接收端:解调信号并评估误码率(BER)。

xiaoheshang_123的博客 273

AUTOSAR从入门到精通-汽车电子电气架构(EEA)(二)

需求的传递并非完全是瀑布式的单向传递,而是双向的,只有被下游接受了的需求才是有效的需求,而且,下游是可以反向对上游提出需求的。当街区的设计完成之后,接下来要做的就是每栋建筑的详细设计,建筑的设计者发布的图纸中规定了房屋的主要结构、所在位置与周边环境的相互关系,以及屋中各种组件的位置与相互之间结合的关系,比如砖混结构、房屋的具体位置和朝向、卧室与客厅的相对位置与尺寸、马桶在卫生间中的位置等,还会对各种建筑材料的选取原则给出具体的标准,比如水泥的标号,钢筋的型号,各种电线的等级要求等等。

getusushu的博客 372

数据结构1-3 灯塔

我自己用的merge排序,只有50通过。待我优化一下。 #include using namespace std; #define maxLength  4000000 typedef long long Rank; class Node{ public: int x; int y; }; class NodeList{ public: Node* _elem;

笨鸟再不努力,没救了 810

Android BSP 开发深度解析:驱动开发、系统定制与调试实战

本文系统介绍了Android BSP开发的核心技术要点,重点探讨了外设驱动开发与调试方法。文章首先解析了Android系统分层架构和BSP的关键作用,详细阐述了Linux内核驱动开发基础,包括字符设备、平台设备和输入子系统驱动的实现方法。随后针对触摸屏、WIFI、蓝牙、USB、摄像头和屏幕等常见外设,深入分析了驱动开发要点和调试技巧。此外,还梳理了Android系统启动流程,介绍了常用调试工具和方法论,并提供了面试常见问题及参考答案。全文内容全面实用,为Android底层开发人员提供了系统的技术指南。

郑伟强dev的专栏 291

排序算法之归并排序

归并排序和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。

9288

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

经典排序算法之归并排序详解

大家好,这篇博客来记录一下归并排序算法的详细实现原理以及归并排序的模板。

晴空๓ 2813

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

【排序算法】—— 归并排序

合并的具体思路:定义三个指针分别指向第一、二个序列和临时序列的第一个元素,然后对指针指向的元素进行比较,将较小的那一个元素添加到临时序列中,随即将对应的指针往后移动一位,直到指向第一个序列或第二个序列的指针走完,因为两个序列都是有序的,所以此时将没有走完的指针所指向的位置的后面所有元素都放入临时序列中即可。因此,我们的问题就得以解决了,第一步就是。来判断此时序列中元素的个数和该子序列在整个序列中的具体位置,然后不断将其从中间进行分割,直到只有一个元素,此时退出递归,回到上一层,进行合并即可。

2302_80243065的博客 2041

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

【十大排序算法】归并排序

归并排序,如同秋日落叶,分散而细碎, 然而风吹叶动,自然而有序, 彼此相遇,轻轻合拢, 最终成就,秩序之谧。

DC1020的博客 1175

排序算法(六)之归并排序(Python代码实现)

目录 一:归并排序算法介绍 二:归并排序的分析 三:归并排序的Python代码实现 四:算法时间复杂度分析 五:常见排序算法效率比较 一:归并排序算法介绍 归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一...

qq_39820671的博客 1520

排序算法(六)——归并排序算法详解及Python实现

一、简介 归并排序(Merging Sort)算法是一种稳定排序算法,和堆排序算法一样,都是利用完全二叉树的深度是⌊logn⌋\lfloor logn\rfloor⌊logn⌋+1 的特性,来提高排序效率,其时间复杂度和堆排序相同,均为O(nlogn)。 二、算法介绍 归并排序就是利用归并(将两个或以上的有序表组合成一个新的有序表)的思想实现的排序方法。 基本原理是:假设初设序列有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1, 然后两两归并,得到⌈n/2⌉\lceil n/2 \rceil⌈

明月清風 1001

常见排序算法之归并排序——归并排序

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

保护小周ღ的博客 2403

排序算法之——归并排序,计数排序

归并排序,计数排序最全详解,图文详解,入坑必看!

Jdxxwu的博客 1733

python排序算法——归并排序(附代码)

(附详细代码)归并排序的平均时间复杂度是O(nlgn),最好情况下的时间复杂度是O(nlgn),最坏情况下的时间复杂度也是O(nlgn)。它的空间复杂度是O(1)。另外,归并排序还是一个稳定的排序算法。

阿极的博客 3795

排序算法图解(六):归并排序

归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。以序列8、4、5、7、1、3、6、2为例分而治之。

黄小黄的博客 857

基础排序算法六:归并排序

基础排序算法六:归并排序 归并排序介绍 将两个的有序数列合并成一个有序数列,我们称之为"归并"。 归并排序(Merge Sort)就是利用归并思想对数列进行排序。根据具体的实现,归并排序包括"从上往下"和"从下往上"2种方式。 从下往上的归并排序:将待排序的数列分成若干个长度为1的子数列,然后将这些数列两两合并;得到若干个长度为2的有序数列,再将这些数列两两合并;得到若干个长度为4的有序数列,...

ccystewart的博客 256

归并排序-排序算法(六)

归并排序 前言 基本思想 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。 首先考虑下如何将2个有序数列合并。这个非常简单,只要从比较2个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。 下面是归并的一个demo //将有序数组a[]和b[]合并到c[]中 void MemeryArray(int a[], int n, int b[], int m, int c[]) { in

Mrkaizi的博客 214

【七大排序算法】之归并排序

七大排序算法完结篇,归并排序是比较难理解的一种排序方法,本篇文章无论是递归实现还是非递归实现都有讲解,包含代码,算法分解图,详细解释等

m0_54469145的博客 2313
上一篇: 基本排序算法 之五 ——快速排序
下一篇: 基本排序算法 之七 ——堆排序
晨晚_
博客等级 码龄9年 10粉丝 17原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值