科普:std::sort干了什么
std::sort算是STL中对OIer比较友好的函数了,但你有想过sort是如何保证它的高速且稳定吗?
正文
我们首先来到第一层:sort函数
template<typename _RandomAccessIterator> inline void sort(_RandomAccessIterator __first, _RandomAccessIterator __last) { //申请使用随机访问迭代器 __glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<_RandomAccessIterator>) //申请使用内置的__gnu_cxx::__ops::__iter_less_iter函数 __glibcxx_function_requires(_LessThanComparableConcept<typename iterator_traits<_RandomAccessIterator>::value_type>) //声明有效区间 __glibcxx_requires_valid_range(__first, __last); //推锅给std::__sort函数 std::__sort(__first, __last, __gnu_cxx::__ops::__iter_less_iter()); }
这一层其实也没干什么,只是把锅推给了第二层:__sort函数
template<typename _RandomAccessIterator, typename _Compare> inline void __sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { if (__first != __last) { //有限制地O(n log n)排序(复杂度优秀,但常数大),将数据几乎有序 std::__introsort_loop(__first, __last, std::__lg(__last - __first) * 2, __comp); //当数据较有序时,用常数比较小的插入排序(复杂度差,但常数优秀) std::__final_insertion_sort(__first, __last, __comp); } }
这里我们就可以见到当年那些大神的神奇操作了:不同的排序方法各司其职,取长补短
接下来我们分开来看,首先看O(n log n)排序的部分:__introsort_loop函数
在所有O(n log n)排序中,常数最优秀的当属快速排序,它自然也成了实现O(n log n)排序的首要选择
当然,直接用快速排序是很可能会被卡的,所以我们要用一个另外的函数兜底
template<typename _RandomAccessIterator, typename _Size, typename _Compare> void __introsort_loop(_RandomAccessIterator __first, _RandomAccessIterator __last, _Size __depth_limit, _Compare __comp) { //如果排序区间较大,复杂度对效率的影响超过了算法的长度,则使用快速排序 while (__last - __first > int(_S_threshold)) { //如果快速排序的层数过大,说明数据对快速排序不友好 if (__depth_limit == 0) { //改用堆排序 std::__partial_sort(__first, __last, __last, __comp); return; } --__depth_limit; //将数据分为两个集合 _RandomAccessIterator __cut = std::__unguarded_partition_pivot(__first, __last, __comp); //将后一半递归排序 std::__introsort_loop(__cut, __last, __depth_limit, __comp); //继续排序前一半 __last = __cut; //其实这个方法很骚,只会向下增加一次递归,另一层用循环代替 //对栈空间的影响比直接两次递归小了不少 } }
小声BB:这个参照值的选取太随便了:__unguarded_partition_pivot函数
template<typename _RandomAccessIterator, typename _Compare> inline _RandomAccessIterator __unguarded_partition_pivot(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { //选取中间值 _RandomAccessIterator __mid = __first + (__last - __first) / 2; //选取first + 1, mid, last - 1的三个位置的中位数作为参照值,并存储在first这个位置上 //里面的函数实现太暴力了,全是if(比暴力还暴力),就不拿出来了 std::__move_median_to_first(__first, __first + 1, __mid, __last - 1, __comp); //快排标准移动,实现如下 return std::__unguarded_partition(__first + 1, __last, __first, __comp); } template<typename _RandomAccessIterator, typename _Compare> _RandomAccessIterator __unguarded_partition(_RandomAccessIterator __first, _RandomAccessIterator __last, _RandomAccessIterator __pivot, _Compare __comp) { //标准的快速排序 while (true) { while (__comp(__first, __pivot)) ++__first; --__last; while (__comp(__pivot, __last)) --__last; if (!(__first < __last)) return __first; std::iter_swap(__first, __last); ++__first; } }
如果用快速排序太卡,就改用堆排序:__partial_sort函数
template<typename _RandomAccessIterator, typename _Compare> inline void __partial_sort(_RandomAccessIterator __first, _RandomAccessIterator __middle, _RandomAccessIterator __last, _Compare __comp) { //建堆 std::__heap_select(__first, __middle, __last, __comp); //弹堆 std::__sort_heap(__first, __middle, __comp); } template<typename _RandomAccessIterator, typename _Compare> void __heap_select(_RandomAccessIterator __first, _RandomAccessIterator __middle, _RandomAccessIterator __last, _Compare __comp) { //建堆 //估计这个算法是用堆得到优先级最大的多个元素,所有会有一个空循环 std::__make_heap(__first, __middle, __comp); for (_RandomAccessIterator __i = __middle; __i < __last; ++__i) if (__comp(__i, __first)) std::__pop_heap(__first, __middle, __i, __comp); } template<typename _RandomAccessIterator, typename _Compare> void __make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { typedef typename iterator_traits<_RandomAccessIterator>::value_type _ValueType; typedef typename iterator_traits<_RandomAccessIterator>::difference_type _DistanceType; if (__last - __first < 2) return; const _DistanceType __len = __last - __first; _DistanceType __parent = (__len - 2) / 2; while (true) { //从堆底向堆顶更新 _ValueType __value = _GLIBCXX_MOVE(*(__first + __parent)); std::__adjust_heap(__first, __parent, __len, _GLIBCXX_MOVE(__value), __comp); if (__parent == 0) return; __parent--; } } template<typename _RandomAccessIterator, typename _Compare> void __sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { //pop and pop while (__last - __first > 1) { --__last; std::__pop_heap(__first, __last, __last, __comp); } }
当然,STL延续了一贯大常数的“祖宗之法”,调整堆和弹堆都如此复杂
template<typename _RandomAccessIterator, typename _Distance, typename _Tp, typename _Compare> void __push_heap(_RandomAccessIterator __first, _Distance __holeIndex, _Distance __topIndex, _Tp __value, _Compare __comp) { //向上跳,直到堆稳定为止 _Distance __parent = (__holeIndex - 1) / 2; while (__holeIndex > __topIndex && __comp(__first + __parent, __value)) { *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __parent)); __holeIndex = __parent; __parent = (__holeIndex - 1) / 2; } *(__first + __holeIndex) = _GLIBCXX_MOVE(__value); } template<typename _RandomAccessIterator, typename _Distance, typename _Tp, typename _Compare> void __adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex, _Distance __len, _Tp __value, _Compare __comp) { //调整堆 //调整方法:先无脑移动到堆底,再向上更新 const _Distance __topIndex = __holeIndex; _Distance __secondChild = __holeIndex; while (__secondChild < (__len - 1) / 2) { __secondChild = 2 * (__secondChild + 1); if (__comp(__first + __secondChild, __first + (__secondChild - 1))) __secondChild--;//选择优先级较高的儿子 *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __secondChild)); __holeIndex = __secondChild;//向下调整 }//如果只有一个儿子 if ((__len & 1) == 0 && __secondChild == (__len - 2) / 2) { __secondChild = 2 * (__secondChild + 1); *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + (__secondChild - 1))); __holeIndex = __secondChild - 1; } //向上更新 std::__push_heap(__first, __holeIndex, __topIndex, _GLIBCXX_MOVE(__value), __gnu_cxx::__ops::__iter_comp_val(__comp)); } template<typename _RandomAccessIterator, typename _Compare> inline void __pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _RandomAccessIterator __result, _Compare __comp) { typedef typename iterator_traits<_RandomAccessIterator>::value_type _ValueType; typedef typename iterator_traits<_RandomAccessIterator>::difference_type _DistanceType; //删除堆顶 _ValueType __value = _GLIBCXX_MOVE(*__result); *__result = _GLIBCXX_MOVE(*__first); //调整堆 std::__adjust_heap(__first, _DistanceType(0), _DistanceType(__last - __first), _GLIBCXX_MOVE(__value), __comp); }
当比较有序时,我们就可以用插入排序优化常数:__final_insertion_sort函数
template<typename _RandomAccessIterator, typename _Compare> void __final_insertion_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { if (__last - __first > int(_S_threshold)) { //先将序列开头排序,作为后面插入排序的预排序区间 std::__insertion_sort(__first, __first + int(_S_threshold), __comp); //将后面的所有元素排序 std::__unguarded_insertion_sort(__first + int(_S_threshold), __last, __comp); } else std::__insertion_sort(__first, __last, __comp);//如果序列较短,就直接排序 }
实现方法也很简单,只是有一点奇怪的操作:
template<typename _RandomAccessIterator, typename _Compare> void __insertion_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { if (__first == __last) return; //标准的插入排序 for (_RandomAccessIterator __i = __first + 1; __i != __last; ++__i) { //如果插入位置为序列开头,那么直接移动整个序列??? //什么骚操作??? if (__comp(__i, __first)) { typename iterator_traits<_RandomAccessIterator>::value_type __val = _GLIBCXX_MOVE(*__i); _GLIBCXX_MOVE_BACKWARD3(__first, __i, __i + 1); *__first = _GLIBCXX_MOVE(__val); } //否则按照标准插入排序去做 else std::__unguarded_linear_insert(__i, __gnu_cxx::__ops::__val_comp_iter(__comp)); } } template<typename _RandomAccessIterator, typename _Compare> inline void __unguarded_insertion_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { //十分老实的插入排序 for (_RandomAccessIterator __i = __first; __i != __last; ++__i) std::__unguarded_linear_insert(__i, __gnu_cxx::__ops::__val_comp_iter(__comp)); } template<typename _RandomAccessIterator, typename _Compare> void __unguarded_linear_insert(_RandomAccessIterator __last, _Compare __comp) { //别看了,这真的就是插入排序 typename iterator_traits<_RandomAccessIterator>::value_type __val = _GLIBCXX_MOVE(*__last); _RandomAccessIterator __next = __last; --__next; while (__comp(__val, __next)) { *__last = _GLIBCXX_MOVE(*__next); __last = __next; --__next; } *__last = _GLIBCXX_MOVE(__val); }
事实上我真的去测试过,在基本有序时,快排真的比插入排序慢(常数太大了)
总结,sort的实现时这样的:
sort( *begin, *end ) { __sort( *begin, *end ) { __introsort_loop( *begin, *end, floor ) { if(/*区间长度较大*/) { if(/*递归层数过大*/) { //堆排序 __partial_sort( *begin, *end ); } //选择参照值,并将元素分离 __cut = __unguarded_partition_pivot( *begin, *end ) //分治 __introsort_loop( *begin, *__cut ); __introsort_loop( *__cut, *end ); } } __final_insertion_sort( *begin, *end ) { //插入排序 } } }
懵逼~~~
看代码看得头晕
Update
发现Luogu有个神贴:https://www.luogu.org/discuss/show/112808
可以发现,如果__last在__first前面,那么就永远不会有__i==__last出现,也就是说,在插入排序时会把__first后面所有的数据全部“排序”,emmm
这内存一定是中暑了,要不我们……
STL这鲁棒性太差了
——会某人