大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来

这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格式化,去掉或者展开用于条件编译或者debug检查的宏,依重要程度重新排序函数,但是不会改变命名方式(虽然MSVC的STL命名实在是我不能接受的那种),对于代码块的解释会在代码块后(下面)用注释标明。

template<class _RanIt, class _Diff, class _Pr> inline
void _Sort(_RanIt _First, _RanIt _Last, _Diff _Ideal, _Pr _Pred)
{
/*
sort的本体部分,解释下参数
_First _Last是边界
_Ideal是与递归深度挂钩的参数
*/
_Diff _Count;
for (; std::_ISORT_MAX < (_Count = _Last - _First) && 0 < _Ideal; )
{
/*
_ISORT_MAX == 32
数组长度<=32的时候或者递归深度 >1.5log2(n)的时候退出快速排序
*/
std::pair<_RanIt, _RanIt> _Mid =
_Unguarded_partition(_First, _Last, _Pred);
_Ideal /= 2, _Ideal += _Ideal / 2;
/*
对整条序列进行划分,同时“递归深度倒计时”变为原来的0.75倍
划分的代码在后面提到
*/
if (_Mid.first - _First < _Last - _Mid.second)
{
_Sort(_First, _Mid.first, _Ideal, _Pred);
_First = _Mid.second;
}
else
{
_Sort(_Mid.second, _Last, _Ideal, _Pred);
_Last = _Mid.first;
}
/*
只选择较短的序列递归进行快速排序
对较长的序列,更新_First和_Last,继续循环
*/
} if (std::_ISORT_MAX < _Count)
{
std::make_heap(_First, _Last, _Pred);
std::sort_heap(_First, _Last, _Pred);
}
/*
结束时的_Count仍然>32的情况,是递归过深的情况,进行堆排序
堆排序就是简单的inplace堆排序,不多说了
*/
else if (2 <= _Count)
{
_Insertion_sort(_First, _Last, _Pred);
}
/*
序列过短而退出,进行简单的插入排序
*/
} template<class _RanIt, class _Pr> inline
void _Med3(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Pr _Pred)
/*
三值取中,接受三个随机访问迭代器,和一个用于判断大小的谓词
结果将使_First, _Mid, _Last按_Pred定义的顺序排列,即中值处于_Mid的位置
*/
{
if (_Pred(*_Mid, *_First))
{
std::iter_swap(_Mid, _First);
}
if (_Pred(*_Last, *_Mid))
{
std::iter_swap(_Last, _Mid);
if (_Pred(*_Mid, *_First))
{
std::iter_swap(_Mid, _First);
}
}
/*
上面的代码本质上是进行了一个三元素的插入排序,按照插入排序的算法原理将循环展开为代码
默认第一个元素排好序
排第二个元素 - 和第一个比较大小 - 不满足_Pred序就交换
前两个元素排序完毕
排第三个元素 - 和第二个比较大小 - 不满足_Pred序就交换
发生交换表明还有可能小于第一个元素,这时比较第一和第二两个元素
*/
} template<class _RanIt, class _Pr> inline
void _Median(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Pr _Pred)
/*
优化版的取中值程序,在数组长度足够大的时候,采用9值取中,更有机会取到最优的中值
*/
{
if (40 < _Last - _First)
{
size_t _Step = (_Last - _First + 1) / 8;
_Med3(_First, _First + _Step, _First + 2 * _Step, _Pred);
_Med3(_Mid - _Step, _Mid, _Mid + _Step, _Pred);
_Med3(_Last - 2 * _Step, _Last - _Step, _Last, _Pred); _Med3(_First + _Step, _Mid, _Last - _Step, _Pred);
/*
当数组长度大于40的时候,采用9值取中,这个40大概是实践得出的较好值吧
个人猜测是如果数组长度小于40仍进行9值取中,得到的优秀的中值做减少的排序代价不足以抵消执行4次3值取中本身的代价 前三个_Med3分别给数组头、中、尾相距_Step的三个元素排序
第四个_Med3给排好序的头、中、尾三组数字的中值继续取中值
结果使得9个值中的中值处于_Mid位置,
*/
}
else
{
_Med3(_First, _Mid, _Last, _Pred);
}
} template<class _RanIt, class _Pr> inline
std::pair<_RanIt, _RanIt> _Unguarded_partition(_RanIt _First, _RanIt _Last, _Pr _Pred)
{
/*
无保护的划分,这个划分与std::partition有本质上的不同
划分考虑值等于pivot的情况,将整个序列分成3段,用一个pair返回他们之间的交界
*/
_RanIt _Mid = _First + (_Last - _First) / 2;
_Median(_First, _Mid, _Last - 1, _Pred);
_RanIt _Pfirst = _Mid;
_RanIt _Plast = _Pfirst + 1;
/*
这里是初始化部分:
执行_Median之后,_Mid是pivot
_Pfirst: == pivot的子序列的左边界
_Plast: == pivot的子序列的右边界 [_Pfirst, _Plast) == pivot
*/
while (_First < _Pfirst
&& !_Pred(*(_Pfirst - 1), *_Pfirst)
&& !_Pred(*_Pfirst, *(_Pfirst - 1)))
{
--_Pfirst;
}
while (_Plast < _Last
&& !_Pred(*_Plast, *_Pfirst)
&& !_Pred(*_Pfirst, *_Plast))
{
++_Plast;
}
/*
进行初始的处理,尽可能的扩展==pivot子序列,这种情况针对大量元素==pivot的情况
此处可以看出,对于已有的判断偏序关系的_Pred,判断两元素相等的情况是当_Pred(a, b)和_Pred(b, a)都不成立的时候
*/ _RanIt _Gfirst = _Plast;
_RanIt _Glast = _Pfirst;
/*
_Gfirst是大于pivot的值的终点
_Glast是小于pivot的值的起点
也即[_Plast, _Gfirst) > pivot
[_Pfirst, _Plast) == pivot
[_Glast, _Pfirst) < pivot
这里命名很迷,提请各位注意 STL的迭代器在一组操作之后,永远保证其左闭右开区间的正确性
这一组操作不应该在操作之后对迭代器进行额外的加减以使其满足左闭右开的性质
他们本身就应该是针对左闭右开区间设计的
*/ for (; ; )
{
for (; _Gfirst < _Last; ++_Gfirst)
{
if (_Pred(*_Pfirst, *_Gfirst))
{
continue;
}
else if (_Pred(*_Gfirst, *_Pfirst))
{
break;
}
else if (_Plast++ != _Gfirst)
{
std::iter_swap(_Plast - 1, _Gfirst);
}
}
/*
把range约定复制过来方便看: [_First, _Glast) 未划分部分
[_Glast, _Pfirst) < pivot
[_Pfirst, _Plast) == pivot
[_Plast, _Gfirst) > pivot
[_Gfirst, _Last) 未划分部分 _Pfirst指向==pivot子序列的第一个
尝试左移_Gfirst迭代器,
if 找到>pivot的元素,继续左移
elif 找到<pivot的元素,停止,此时_Gfirst指向那个>pivot的元素
else 找到=pivot的元素,这个元素和>pivot的第一个元素交换,同时=pivot序列右侧增长
比如
555568765
^ ^
交换后
555558766
^ ^
第一个^是_Plast,第二个^是_Gfirst
结束时,_Gfirst永远指向一个<pivot的元素(或者==_Last)
符合我的说法:[_Plast, _Gfirst)是个 > pivot的左开右闭区间,_Gfirst恰好不满足>pivot
*/ for (; _First < _Glast; --_Glast)
{
if (_Pred(*(_Glast - 1), *_Pfirst))
{
continue;
}
else if (_Pred(*_Pfirst, *(_Glast - 1)))
{
break;
}
else if (--_Pfirst != _Glast - 1)
{
std::iter_swap(_Pfirst, _Glast - 1);
}
}
/*
原理同上,左移_Glast
结束时,_Glast指向一个>pivot的元素(或者==_First)
*/ /*
[_First, _Glast) 未划分部分
[_Glast, _Pfirst) < pivot
[_Pfirst, _Plast) == pivot
[_Plast, _Gfirst) > pivot
[_Gfirst, _Last) 未划分部分
*/ if (_Glast == _First && _Gfirst == _Last)
{
return (pair<_RanIt, _RanIt>(_Pfirst, _Plast));
}
/*
如果_Glast和_Gfirst都到达了终点,就返回,_Pfirst, _Plast代表什么上面有
*/ if (_Glast == _First)
{
if (_Plast != _Gfirst)
{
std::iter_swap(_Pfirst, _Plast);
}
++_Plast;
std::iter_swap(_Pfirst++, _Gfirst++);
}
/*
如果<pivot的部分已经排好,那么进行一次滚动交换,==pivot的第一个和>pivot的第一个交换,
>pivot的区间左边界右移
_Pfirst现在指向一个>pivot的元素,_Pfirst和_Gfirst交换,然后两个迭代器都右移
比如
1423245555556978672xxx
^ ^ ^
第一次交换
1423246555555978672xxx
^ ^ ^
第二次交换
1423242555555978676xxx
^ ^ ^
*/ else if (_Gfirst == _Last)
{ // no room at top, rotate pivot downward
if (--_Glast != --_Pfirst)
{
std::iter_swap(_Glast, _Pfirst);
}
std::iter_swap(_Pfirst, --_Plast);
}
/*
和上面相同,进行一次左侧的滚动交换,不多赘述
*/ else
{
std::iter_swap(_Gfirst++, --_Glast);
} /*
左右两个迭代器都没有到达边界,直接交换并移动即可
*/
}
} template<class _RanIt, class _Pr> inline
void sort(_RanIt _First, _RanIt _Last, _Pr _Pred)
{
_Sort(_Unchecked(_First), _Unchecked(_Last), _Last - _First, _Pred);
}
/*
sort的向外暴露包装,不再赘述
*/
template<class _RanIt> inline
void sort(_RanIt _First, _RanIt _Last)
{
std::sort(_First, _Last, less<>());
}
/*
sort的向外暴露包装,不再赘述
*/ template<class _BidIt1, class _BidIt2> inline
_BidIt2 _Move_backward(_BidIt1 _First, _BidIt1 _Last, _BidIt2 _Dest, _Nonscalar_ptr_iterator_tag)
{ // move [_First, _Last) backwards to [..., _Dest), arbitrary iterators
while (_First != _Last)
*--_Dest = std::move(*--_Last);
return (_Dest);
} template<class _InIt, class _OutIt> inline
_OutIt _Move_backward(_InIt _First, _InIt _Last, _OutIt _Dest, _Scalar_ptr_iterator_tag)
{ // move [_First, _Last) backwards to [..., _Dest), pointers to scalars
ptrdiff_t _Count = _Last - _First;
::memmove(&*_Dest - _Count, &*_First,
_Count * sizeof(*_First));
return (_Dest - _Count);
}
/*
针对POD类型和非POD类型的实现,其中POD类型使用memmove,更快,非POD类型使用std::move,相比C++9803更快
*/ template<class _BidIt1, class _BidIt2> inline
_BidIt2 _Move_backward(_BidIt1 _First, _BidIt1 _Last, _BidIt2 _Dest)
{ // move [_First, _Last) backwards to [..., _Dest), unchecked
return (_Move_backward(_First, _Last,
_Dest, _Ptr_cat(_First, _Dest)));
}
/*
内存移动的包装
*/ template<class _BidIt, class _Pr, class _Ty> inline
void _Insertion_sort1(_BidIt _First, _BidIt _Last, _Pr _Pred, _Ty *)
{
/*
这部分的迭代器约定
[_First, _Next) 排好的部分
[_Next, _Last) 没排好的部分
*/
if (_First != _Last)
{
for (_BidIt _Next = _First; ++_Next != _Last; )
{
_BidIt _Next1 = _Next;
_Ty _Val = _Move(*_Next); if (_Pred(_Val, *_First))
{
_Move_backward(_First, _Next, ++_Next1);
*_First = _Move(_Val);
/*
如果要插入的元素比已经排好的第一个元素还要小,那么直接把已经排好的部分右移,第一个元素放进去
这里针对大部分已经排好的序列优化
*/
}
else
{
for (_BidIt _First1 = _Next1; _Pred(_Val, *--_First1); _Next1 = _First1)
{
*_Next1 = _Move(*_First1);
}
*_Next1 = _Move(_Val);
/*
否则正常的挨个比较并移动
*/
}
}
}
} template<class _BidIt, class _Pr> inline
void _Insertion_sort(_BidIt _First, _BidIt _Last, _Pr _Pred)
{ // insertion sort [_First, _Last), using _Pred
_Insertion_sort1(_First, _Last, _Pred, _Val_type(_First));
/*
插入排序不安全版实现的wrap
*/
}

源码阅读笔记 - 1 MSVC2015中的std::sort的更多相关文章

  1. CI框架源码阅读笔记5 基准测试 BenchMark.php

    上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功 ...

  2. CI框架源码阅读笔记4 引导文件CodeIgniter.php

    到了这里,终于进入CI框架的核心了.既然是“引导”文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.c ...

  3. CI框架源码阅读笔记3 全局函数Common.php

    从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap ...

  4. CI框架源码阅读笔记2 一切的入口 index.php

    上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里再次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中, ...

  5. Three.js源码阅读笔记-5

    Core::Ray 该类用来表示空间中的“射线”,主要用来进行碰撞检测. THREE.Ray = function ( origin, direction ) { this.origin = ( or ...

  6. PHP源码阅读笔记一(explode和implode函数分析)

    PHP源码阅读笔记一一.explode和implode函数array explode ( string separator, string string [, int limit] )此函数返回由字符 ...

  7. AQS源码阅读笔记(一)

    AQS源码阅读笔记 先看下这个类张非常重要的一个静态内部类Node.如下: static final class Node { //表示当前节点以共享模式等待锁 static final Node S ...

  8. libevent源码阅读笔记(一):libevent对epoll的封装

    title: libevent源码阅读笔记(一):libevent对epoll的封装 最近开始阅读网络库libevent的源码,阅读源码之前,大致看了张亮写的几篇博文(libevent源码深度剖析 h ...

  9. jdk源码阅读笔记-LinkedHashMap

    Map是Java collection framework 中重要的组成部分,特别是HashMap是在我们在日常的开发的过程中使用的最多的一个集合.但是遗憾的是,存放在HashMap中元素都是无序的, ...

随机推荐

  1. 0518Scrum项目5.0

    1.团队成员完成自己认领的任务. 2.燃尽图:理解.设计并画出本次Sprint的燃尽图的理想线.参考图6. 3.每日立会更新任务板上任务完成情况.燃尽图的实际线,分析项目进度是否在正轨.    每天的 ...

  2. Oracle错误:动态执行表不可访问,本会话自动统计被禁止,关闭自动统计之后的问题

    使用PL/SQL时, 每次第一次打开表的时候会提示"动态执行表不可访问,本会话的自动统计被禁止"的错误,一消息如下: V$SESSION,V$SESSTAT,V$STATNAME没 ...

  3. [转]HttpURLConnection的使用

    /* * URL请求的类别分为二类,GET与POST请求.二者的区别在于: * a:) get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet, * b:) post与get ...

  4. MagicalRecord的使用(第三方库实现的数据库)

    MagicalRecord:http://cocoadocs.org/docsets/MagicalRecord/2.1/ 安装: 1.新建一个工程,注意不要勾选 Core Data. 2.利用Coc ...

  5. C语言基础--数组及相关

    概念: 一堆相同类型的数据的有序集合 格式: 元素类型  数组名称[ 元素个数 ] 定义数组: // 定义了一个名称叫做scores的数组, 数组中可以存放3个int类型的数据 ]; // 只要定义一 ...

  6. Mobiscroll 3.0 官方同步版

    Mobiscroll 3.0 官方同步版发布了. Mobiscroll是一个用于触摸设备的日期和时间选择器,它的使用不会改变HTML5.PhoneGap以及混合应用的原生用户体验.作为一款jQuery ...

  7. python3 使用pyperclip读写剪贴板(windows)

    2016年5月14日 03:41:38 codegay 使用pyperclip库读写剪贴板非常简单~, 1.使用命令安装: pip install pyperclip 2.然后...就可以了: 以下是 ...

  8. linux命令:whereis

    1.命令介绍: whereis用来查找二进制文件,源代码文件和帮助说明文件,whereis是在系统的数据库文件中查找,所以速度非常快,但是系统的文件数据库不是实时更新的,默认一个星期更新一次. 2.命 ...

  9. 1、JS的数据类型

    一.分类:JS有6种数据类型: 1.string 2.undefined 3.number 4.null 5.boolean 6.object(包含函数.数组.日期等) 二.隐式转换 1.+和- va ...

  10. 如何使用git 跟进项目进程

    首先,git能够记录版本信息,方便大家在任何时刻能够取回之前的项目版本,其次可以记录分支信息,方便进行分支作业. Step1:cd到你的项目根目录下,从团队github 项目clone到本地. 命令如 ...