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


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)的元素到AT* 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//并且记录下数组长度lenint 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++];}elseC[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] 条件判断的时间!)所以,代码中没有加入这一考虑。 当然,这样在最好的情况下就会更耗时!

基本排序算法 之六 ——归并排序相关推荐

  1. 排序算法:归并排序、快速排序

    相关博客: 排序算法:冒泡排序.插入排序.选择排序.希尔排序 排序算法:归并排序.快速排序 排序算法:桶排序.计数排序.基数排序 排序算法:堆排序 十大排序算法小结 一.归并排序: 1.工作原理: 归 ...

  2. 排序算法之--归并排序(好玩的一个算法o。o)快速入门

    排序算法之--归并排序(好玩的一个算法o.o) 下面是归并操作的基本思路(注意:是归并操作哦,不是归并排序哦) 归并操作的工作原理如下: 第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存 ...

  3. NOI提高级:排序算法之归并排序、快速排序

    图解排序算法(四)之归并排序 图解排序算法(四)之归并排序 - dreamcatcher-cx - 博客园 小学生图解排序算法:⑥归并排序 小学生图解排序算法:⑥归并排序_纯文笔记-CSDN博客_图解 ...

  4. 数据结构与算法:十大排序算法之归并排序

    数据结构与算法:十大排序算法之归并排序 package TopTenSortingAlgorithms;/*** 归并排序:Java** @author skywang* @date 2014/03/ ...

  5. [Alg]排序算法之归并排序

    [Alg]排序算法之归并排序 作者:屎壳郎 miaosg01@163.com 日期:Aug 2021 版次:初版 简介: 归并排序是一类在任何情况下都能保证Nlg⁡(N)N\lg(N)Nlg(N)的排 ...

  6. 【排序算法】归并排序(C语言)

    [排序算法]-- 归并排序(C语言) 目录 一.归并排序的原理 二.两个有序数组排序和合并 1. 原地排序 2. 创建临时空间 二.递归实现 三.非递归实现 1. 实现思路 2. 数组边界问题 3. ...

  7. 排序算法中——归并排序和快速排序

    冒泡排序.插入排序.选择排序这三种算法的时间复杂度都为 $O(n^2)$,只适合小规模的数据.今天,我们来认识两种时间复杂度为 $O(nlogn)$ 的排序算法--归并排序(Merge Sort)和快 ...

  8. 【DS】排序算法之归并排序(Merge Sort)

    一.算法思想 归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法的一个非常典型的应用,指的是将两个已经排序的序列合并成一个序列的操作.其归并思想如下: 1)申请空间,使其大小为两个已经 ...

  9. c语言归并排序代码详细注释,C语言实现排序算法之归并排序详解

    排序算法中的归并排序(Merge Sort)是利用"归并"技术来进行排序.归并是指将若干个已排序的子文件合并成一个有序的文件. 一.实现原理: 1.算法基本思路 设两个有序的子文件 ...

最新文章

  1. bzoj 1004: [HNOI2008]Cards
  2. powertoys中文版
  3. 文件安全传输服务器,安全传输:从文件加载服务器证书
  4. php 判断5张牌是不是顺子,从扑克牌中随机抽取5张牌,判断是不是一个顺子,即这5张牌是不是连续(面试题)...
  5. 字符串函数 replace() 方法妙用
  6. 告别花瓶:2015年智能电视路在何方?
  7. (一)容器从入门到深入-容器和镜像
  8. Linux下OneinStack一键安装JAVA+PHP+Tomcat+Nginx+MySQL网站环
  9. Linux C 多线程编程----互斥锁与条件变量-转
  10. C++ ORM ODB入门
  11. hibernate 镜像下载_虚拟光驱软件下载手机版-虚拟光驱免费中文版64位下载v5.8.0...
  12. iOS动态库和静态库的运用
  13. 树莓派安装各种Ubuntu版本及系统推荐
  14. 使用css修改input的文字提示语颜色
  15. 计算机ms高级应用科目一 科目二考什么,科目一、科目二、科目三、科目四都考什么?全都在这儿了!...
  16. 如何在Godot中使用自发光材质
  17. 如何实现视频平台会员多账号登录
  18. 基于微信小程序云开发的投票小程序源码,图文投票微信小程序源码
  19. 微信小程序优惠劵功能(包含用户需求,axure原型设计,数据库设计,后台功能,微信小程序功能)
  20. js 删除list中的某个元素 向list中添加某个元素

热门文章

  1. 【转】最实用的IT类网站及工具大集合
  2. LVGL-gui_user.h
  3. 前端人脸识别解决方案
  4. 闪电网络如何实现更加去中心化的网络
  5. Oracle 中常用的字符串函数总结
  6. FIO 存储性能压测
  7. C语言之for循环与while循环
  8. 深入理解散列函数和散列表
  9. Visual Studio 2008 项目安装和部署
  10. 推荐一款适用于vue的h5富文本编辑器