源码阅读笔记 - 1 MSVC2015中的std::sort
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码阅读笔记 - 1 MSVC2015中的std::sort相关的知识,希望对你有一定的参考价值。
大约寒假开始的时候我就已经把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的主要内容,如果未能解决你的问题,请参考以下文章
MSVC 2015 为 std::vector 选择不正确的构造函数重载
C++ 多个版本的默认特殊成员函数 -- MSVC 2015 中的错误
std::ofstream 无法在 win7/64 和 msvc2013 上使用 std::ios::ate 打开大文件