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

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] 条件判断的时间!)所以,代码中没有加入这一考虑。 当然,这样在最好的情况下就会更耗时!

手把手教你为Multisim 14.0创建自定义元件:以SPICE模型导入为例 本文详细介绍了如何在Multisim 14.0中创建自定义元件,重点讲解了SPICE模型的导入与应用。通过系统性的步骤指南,包括元件符号设计、引脚定义、SPICE模型关联及验证测试,帮助工程师高效扩展元件库,满足特定设计需求。文章还提供了故障排除和性能优化的实用技巧,确保自定义元件在实际仿真中的可靠性。 阅读详情

相关推荐

王爽《汇编语言》课程设计2(不涉及软盘版本)

assume cs:code,ds:data,ss:stack stack segment db 128 dup (0) stack ends data segment db 256 dup (0) data ends code segment start: mov ax,stack mov ss,ax mov sp,128 ...

weixin_41944412的博客 1178

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

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

_TFBoy的小茶馆 489

【毕业设计参考】基于yolov8+pyqt5自适应界面设计的锂电池表面缺陷检测系统

基于YOLOv8+PyQt5的锂电池表面缺陷检测系统结合了先进的目标检测技术与友好的用户界面设计,为锂电池质量检测提供了一种高效、准确的解决方案。未来,随着深度学习技术的不断发展,我们可以进一步优化模型,提高检测精度和速度,同时拓展系统功能,如实现实时视频流检测、与生产系统的深度集成等,为锂电池产业的发展注入新的活力。希望本文对大家在相关领域的研究和实践有所帮助,欢迎大家在评论区交流讨论。

1182

归并排序-CPP实现

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

CODER-GODV的博客 395

保姆级教程:在PX4 Gazebo仿真中为Iris无人机挂载Intel D435i深度相机(含完整配置流程)

本文提供在PX4 Gazebo仿真环境中为Iris无人机挂载Intel D435i深度相机的完整配置流程,涵盖环境准备、模型部署、插件安装及调试技巧。通过详细步骤和常见问题解决方案,帮助开发者快速搭建深度相机仿真环境,适用于无人机视觉开发与算法测试。

weixin_28688385的博客 338

量化平台深度测评:聚宽 / 米筐 / 迅投 QMT 对比分析

本文对比测评国内主流量化交易平台聚宽、米筐和迅投QMT,从回测性能、实盘稳定性和高级功能三方面展开分析。测试显示:聚宽在低频策略回测中效率最高(128秒),米筐次之;迅投QMT凭借C++直连架构实现最低延迟(150ms)和最高订单撤销成功率(98.5%)。平台特色方面,QMT的智能拆单算法可减少25%滑点损失,聚宽的因子库支持版本控制和缓存复用。建议低频策略选聚宽,高频交易选QMT,团队协作优先考虑聚宽。测试数据均来自合规实盘环境,建议根据策略特性进行试用验证。

tianchi2009123的博客 7377

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

磁传感器技术解析:从霍尔效应到车规级应用,纳芯微的本土第一之路

磁传感器是现代工业与汽车电子中的关键感知元件,其技术演进经历了从分立器件到高集成度SoC的跨越。霍尔效应与磁阻效应构成两大物理基础,其中硅基霍尔方案凭借高性价比与成熟供应链,在车规级应用中占据主流。高精度电流检测、角度与位置感知、磁开关等场景,对传感器的温漂抑制、共模瞬态抗扰与批量一致性提出严苛要求。车规级可靠性体系与系统级集成能力,成为衡量供应商的核心标尺。随着新能源主驱、光伏逆变、工业伺服等应用爆发,具备完整信号链与隔离技术积累的厂商正逐步构建护城河。本文以纳芯微为样本,剖析其从隔离技术延伸至磁传感器平

weixin_34387468的博客 365

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

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

程序员的时光 2万+

GEE APP:基于Sentinel-2遥感影像的植被指数(NDVI)和水体指数(NDWI)可视化应用

本文介绍了利用Google Earth Engine(GEE)平台处理Sentinel-2卫星数据的方法,重点讲解NDVI(归一化植被指数)和NDWI(归一化水体指数)的计算与应用。内容包括环境准备、数据加载与处理、指数计算原理、可视化方法以及实际应用案例。教程详细展示了如何定义研究区域、加载影像数据、进行指数运算,并生成专业统计报告和图例。文章还提供了常见问题解答和最佳实践建议,为生态环境监测、农业评估和水资源管理等场景提供了实用工具。通过JavaScript代码示例,读者可以快速掌握GEE平台的遥感影像

此星光明博客 273

BF8815A

了解ADBMS1818芯片的人会发现,这款BF8815B和1818有着很多相似的东西,当你了解其中一款AFE之后,另一款你的上手也会特别快,下面文章是我针对1818梳理的一些重要知识点,此篇文章主要讲解BF8815B的一些差异点。

m0_61973119的博客 561

数据结构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

深入解析:AndroidManifest.xml 与 Qt 6.7.0 的整合

本文探讨了在Qt 6.7.0和CMake构建Android应用时,AndroidManifest.xml未被正确识别的问题。通过分析CMake配置错误、文件路径问题等可能原因,提出了在CMakeLists.txt中明确指定路径、确保目录结构正确等解决方案,并给出了示例项目演示。文章强调正确配置AndroidManifest.xml对获取权限和应用功能的重要性,为Qt开发者提供了实用的调试建议。

2501_90323865的博客 110

排序算法之归并排序

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

9288

计算机控制技术及应用

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

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

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

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

晴空๓ 2813

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

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

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

2302_80243065的博客 2041

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

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

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

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、付费专栏及课程。

余额充值