两个连在一起的序列 [first, middle) 和 [middle, last) 都已经排序,

归并排序最核心的算法就是 将 [first, middle) 和 [middle, last) 在 O(N)时间内合并成一个有序数组。

但是合并的过程中一般需要  m + n 的额外辅助空间。其中, m 、 n 是数组的左右半边的长度。

现在假如,

1〉 辅助空间 bufSize < m + n 呢, 但是比 min(m, n) 大。也就是说能够容纳序列1 或者 序列 2。

2〉 bufSize < min(m, n) 呢??

3〉假如没有辅助内存呢??

STL implace_merge 函数在其实现中分别考虑了上述三种情况,并且尽可能地使效率比较高。

记号:长度为 m 的有序数组 A, 长度为 n 的有序数组 B,

一、 buffer 能够容纳其中的一个有序数组

1> 能够容纳 序列 1 [first, middle)

这时候只要先把 [first, middle) 拷贝到 [buffer, end_buffer) 中,

然后进行常规的 Merge, 依次将 [middle, last) 和 [buffer, end_buffer) 中小的元素放到 [first, last) 即可。不会存在数据覆盖的问题。

STL 源码如下:

template <class _BidirectionalIter, class _Distance, class _Pointer>
void __merge_adaptive(_BidirectionalIter __first,
_BidirectionalIter __middle,
_BidirectionalIter __last,
_Distance __len1, _Distance __len2,
_Pointer __buffer, _Distance __buffer_size) {
if (__len1 <= __len2 && __len1 <= __buffer_size) {
_Pointer __buffer_end = copy(__first, __middle, __buffer);
merge(__buffer, __buffer_end, __middle, __last, __first);
}

copy(first, _middle, _buffer) 就是将 [first, middle) 复制到 buffer 中,

然后调用 STL 库的merge。

2> 能够容纳 序列 2 [middle,last)

这时候将 [middle, last) 复制到 [buffer, end_buffer) 中,然后 逆向 merge 即可。

从两个序列的尾部向前,依次将大的元素放到数组的尾部。。

  else if (__len2 <= __buffer_size) {
_Pointer __buffer_end = copy(__middle, __last, __buffer);
__merge_backward(__first, __middle, __buffer, __buffer_end, __last);
}
template <class _BidirectionalIter1, class _BidirectionalIter2,
          class _BidirectionalIter3, class _Compare>
_BidirectionalIter3 __merge_backward(_BidirectionalIter1 __first1,
                                     _BidirectionalIter1 __last1,
                                     _BidirectionalIter2 __first2,
                                     _BidirectionalIter2 __last2,
                                     _BidirectionalIter3 __result,
                                     _Compare __comp) {
  if (__first1 == __last1)
    return copy_backward(__first2, __last2, __result);
  if (__first2 == __last2)
    return copy_backward(__first1, __last1, __result);
  --__last1;
  --__last2;
  while (true) {
    if (__comp(*__last2, *__last1)) {
      *--__result = *__last1;
      if (__first1 == __last1)
        return copy_backward(__first2, ++__last2, __result);
      --__last1;
    }
    else {
      *--__result = *__last2;
      if (__first2 == __last2)
        return copy_backward(__first1, ++__last1, __result);
      --__last2;
    }
  }
}

二、buffer 大小不足以容纳 [first, middle) 和 [middle,last)

上面的两种思路其实挺巧的,但是现在buffer 更小,怎么办??

采取分治的思想,将问题的规模降下来,递归调用子问题,直到对于子问题,这个Buffer 大小足以容纳某一个有序序列。

我们的思路是:将数组分成  【1】  【2】  【3】  【4】 四个小数组。

交换 [2]  [3],使得 【1 3】 作为新的子问题, 递归调用;

【2 4】作为新的子问题, 递归调用;

注意: 要使得最终数组有序,必须满足 【1 3】 中所有元素 <= 【2 4】中所有元素(这就是我们切数组时要满足的要求)

具体来说,

假如 [first, middle) 长度小于 [middle, last)。

STEP 1:

我们拿长的序列开刀,对半开。

STEP 2:

在对 [middle, first) 切时,要满足 数组 【3】 中元素 <= 数组【2】中的元素,

【2】中元素 <= 数组【4】中的元素

Bingo, 其实只要在 [middle, last) 中 二分搜索【1】中最后一个元素 5。

STEP 3:

将数组 【2】和数组【3】rotate 即可。

STEP 4:

递归调用 【1】【3】, 和 【2】【4】;

直到 bufferSize >= 序列1或者序列2.

完整代码如下:

template <class _BidirectionalIter, class _Distance, class _Pointer>
void __merge_adaptive(_BidirectionalIter __first,
_BidirectionalIter __middle,
_BidirectionalIter __last,
_Distance __len1, _Distance __len2,
_Pointer __buffer, _Distance __buffer_size) {
if (__len1 <= __len2 && __len1 <= __buffer_size) {
_Pointer __buffer_end = copy(__first, __middle, __buffer);
merge(__buffer, __buffer_end, __middle, __last, __first);
}
else if (__len2 <= __buffer_size) {
_Pointer __buffer_end = copy(__middle, __last, __buffer);
__merge_backward(__first, __middle, __buffer, __buffer_end, __last);
}
else {
_BidirectionalIter __first_cut = __first;
_BidirectionalIter __second_cut = __middle;
_Distance __len11 = 0;
_Distance __len22 = 0;
if (__len1 > __len2) {
__len11 = __len1 / 2;
advance(__first_cut, __len11);
__second_cut = lower_bound(__middle, __last, *__first_cut);
distance(__middle, __second_cut, __len22);
}
else {
__len22 = __len2 / 2;
advance(__second_cut, __len22);
__first_cut = upper_bound(__first, __middle, *__second_cut);
distance(__first, __first_cut, __len11);
}
_BidirectionalIter __new_middle =
__rotate_adaptive(__first_cut, __middle, __second_cut, __len1 - __len11,
__len22, __buffer, __buffer_size);
__merge_adaptive(__first, __first_cut, __new_middle, __len11,
__len22, __buffer, __buffer_size);
__merge_adaptive(__new_middle, __second_cut, __last, __len1 - __len11,
__len2 - __len22, __buffer, __buffer_size);
}
}

参考资料:

侯捷, 《STL 源码分析》

STL源码分析《3》----辅助空间不足时,如何进行归并排序的更多相关文章

  1. STL源码分析读书笔记--第二章--空间配置器(allocator)

    声明:侯捷先生的STL源码剖析第二章个人感觉讲得蛮乱的,而且跟第三章有关,建议看完第三章再看第二章,网上有人上传了一篇读书笔记,觉得这个读书笔记的内容和编排还不错,我的这篇总结基本就延续了该读书笔记的 ...

  2. STL源码分析《4》----Traits技术

    在 STL 源码中,到处可见 Traits 的身影,其实 Traits 不是一种语法,更确切地说是一种技术. STL库中,有一个函数叫做 advance, 用来将某个迭代器(具有指针行为的一种 cla ...

  3. STL 源码分析《1》---- list 归并排序的 迭代版本, 神奇的 STL list sort

    最近在看 侯捷的 STL源码分析,发现了以下的这个list 排序算法,乍眼看去,实在难以看出它是归并排序. 平常大家写归并排序,通常写的是 递归版本..为了效率的考虑,STL库 给出了如下的 归并排序 ...

  4. stl源码分析之allocator

    allocator封装了stl标准程序库的内存管理系统,标准库的string,容器,算法和部分iostream都是通过allocator分配和释放内存的.标准库的组件有一个参数指定使用的allocat ...

  5. STL 源码分析《2》----nth_element() 使用与源码分析

    Select 问题: 在一个无序的数组中 找到第 n 大的元素. 思路 1: 排序,O(NlgN) 思路 2: 利用快排的 RandomizedPartition(), 平均复杂度是 O(N) 思路 ...

  6. STL源码分析与实现-stl_list容器

    1. stl_list 介绍 今天我们来总结一下stl_List, 通过之前介绍单链表的文章,其实对链表的基本操作已经十分熟悉了,那对于stl_list,无非就是链表结构不一样,至于其中的增删改查的细 ...

  7. STL 源码分析六大组件-allocator

    1. allocator 基本介绍 分配器(allocator))是C ++标准库的一个组件, 主要用来处理所有给定容器(vector,list,map等)内存的分配和释放.C ++标准库提供了默认使 ...

  8. STL源码分析之迭代器

    前言 迭代器是将算法和容器两个独立的泛型进行调和的一个接口. 使我们不需要关系中间的转化是怎么样的就都能直接使用迭代器进行数据访问. 而迭代器最重要的就是对operator *和operator-&g ...

  9. stl源码分析之vector

    上篇简单介绍了gcc4.8提供的几种allocator的实现方法和作用,这是所有stl组件的基础,容器必须通过allocator申请分配内存和释放内存,至于底层是直接分配释放内存还是使用内存池等方法就 ...

随机推荐

  1. Matlab安装记录 - LED Control Activex控件安装

    Matlab安装记录-LED Control Activex控件安装 2013-12-01  22:06:36 最近在研究Matlab GUI技术,准备用于制作上位机程序:在Matlab GUI的技术 ...

  2. DSP EPWM学习笔记1 - EPWM定时中断

    DSP EPWM学习笔记1 - EPWM定时中断 彭会锋 EPWM模块组成 EPWM有7个子模块组成:时间基准 TB.比较功能 CC.动作限定 AQ.死区产生 DB.斩波控制 PC.故障捕获 TZ.事 ...

  3. Android 反编译工具简介

    Android 反编译工具: 所需工具:1 apktool : 用于获取资源文件 2 dex2Jar : 用于将classes.dex转化成jar文件 2 jd-gui: 将jar文件转化成java文 ...

  4. Java开发 Eclipse使用技巧(转)

    1.如何设置默认的代码目录为src,默认的输出目录为bin? window->Preferences->java->Build Path中,右侧选择Folders就可以 2.如何为快 ...

  5. Maven(1)-安装和配置

    Maven(1)-安装和配置 一.本机必须安装好Jdk 二 .maven下载 http://maven.apache.org/download.cgi ,下载后把maven-bin解压到自己的目录即可 ...

  6. JDE客户端get时报错“ERROR:fetch from table F0101 failed”

    客户端开发时发现总报错误“ERROR:fetch from table F0101 failed” 原因是用户ID在地址名册中找不到地址号.修改用户地址号即可.如下图所示

  7. [转]初探Struts2.0

    本文转自:http://blog.csdn.net/kgd1120/article/details/1667301 Struts作为MVC 2的Web框架,自推出以来不断受到开发者的追捧,得到用广泛的 ...

  8. gitlab配置邮件通知

    配置用户提交评论.添加issue等的邮件通知: Gitlab邮件提醒方便跟踪项目进度,在这里介绍两种方式,一种是用系统的sendmail发送邮件,另一种是GMAIL的stmp来发送邮件 第一种 用系统 ...

  9. tomcat 集群配置,Session复制共享

    本配置在tomcat7上验证通过.通过此方法配置的集群,session信息将会被自动复制到各个节点. 1.配置Server.xml 在Server.xml中,找到被注释<Cluster/> ...

  10. centos 卸载软件·

    centos下完全卸载php 1显示相关软件的列表 rpm -qa  | grep i(可以不加) php 2 卸载即可 rpm -e 软件名 --nodeps centos下完全卸载mysql 1显 ...