科普: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) * , __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 == )
{
//改用堆排序
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) / ;
//选取first + 1, mid, last - 1的三个位置的中位数作为参照值,并存储在first这个位置上
//里面的函数实现太暴力了,全是if(比暴力还暴力),就不拿出来了
std::__move_median_to_first(__first, __first + , __mid, __last - , __comp);
//快排标准移动,实现如下
return std::__unguarded_partition(__first + , __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 < )
return; const _DistanceType __len = __last - __first;
_DistanceType __parent = (__len - ) / ;
while (true)
{
//从堆底向堆顶更新
_ValueType __value = _GLIBCXX_MOVE(*(__first + __parent));
std::__adjust_heap(__first, __parent, __len, _GLIBCXX_MOVE(__value), __comp);
if (__parent == )
return;
__parent--;
}
} template<typename _RandomAccessIterator, typename _Compare>
void __sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
{
//pop and pop
while (__last - __first > )
{
--__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 - ) / ;
while (__holeIndex > __topIndex && __comp(__first + __parent, __value))
{
*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __parent));
__holeIndex = __parent;
__parent = (__holeIndex - ) / ;
}
*(__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 - ) / )
{
__secondChild = * (__secondChild + );
if (__comp(__first + __secondChild, __first + (__secondChild - )))
__secondChild--;//选择优先级较高的儿子
*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __secondChild));
__holeIndex = __secondChild;//向下调整
}//如果只有一个儿子
if ((__len & ) == && __secondChild == (__len - ) / )
{
__secondChild = * (__secondChild + );
*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + (__secondChild - )));
__holeIndex = __secondChild - ;
}
//向上更新
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(), _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 + ; __i != __last; ++__i)
{
//如果插入位置为序列开头,那么直接移动整个序列???
//什么骚操作???
if (__comp(__i, __first))
{
typename iterator_traits<_RandomAccessIterator>::value_type __val = _GLIBCXX_MOVE(*__i);
_GLIBCXX_MOVE_BACKWARD3(__first, __i, __i + );
*__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这鲁棒性太差了
——会某人
科普:std::sort干了什么的更多相关文章
- 将三维空间的点按照座标排序(兼谈为std::sort写compare function的注意事项)
最近碰到这样一个问题:我们从文件里读入了一组三维空间的点,其中有些点的X,Y,Z座标只存在微小的差别,远小于我们后续数据处理的精度,可以认为它们是重复的.所以我们要把这些重复的点去掉.因为数据量不大, ...
- 源码阅读笔记 - 1 MSVC2015中的std::sort
大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格 ...
- c++ std::sort函数调用经常出现的invalidate operator<错误原因以及解决方法
在c++编程中使用sort函数,自定义一个数据结构并进行排序时新手经常会碰到这种错误. 这是为什么呢?原因在于什么?如何解决? 看下面一个例子: int main(int, char*[]) { st ...
- std::sort引发的core
#include <stdio.h> #include <vector> #include <algorithm> #include <new> str ...
- 一个std::sort 自定义比较排序函数 crash的分析过程
两年未写总结博客,今天先来练练手,总结最近遇到的一个crash case. 注意:以下的分析都基于GCC4.4.6 一.解决crash 我们有一个复杂的排序,涉及到很多个因子,使用自定义排序函数的st ...
- Qt使用std::sort进行排序
参考: https://blog.csdn.net/u013346007/article/details/81877755 https://www.linuxidc.com/Linux/2017-01 ...
- 非常无聊——STD::sort VS 基数排序
众所周知,Std::sort()是一个非常快速的排序算法,它基于快排,但又有所修改.一般来说用它就挺快的了,代码一行,时间复杂度O(nlogn)(难道不是大叫一声“老子要排序!!”就排好了么...). ...
- 今天遇到的一个诡异的core和解决 std::sort
其实昨天开发pds,就碰到了core,我还以为是内存不够的问题,或者其他问题. 今天把所有代码挪到了as这里,没想到又出core了. 根据直觉,我就觉得可能是std::sort这边的问题. 上网一搜, ...
- std::sort的详细用法
#include <algorithm> #include <functional> #include <array> #include <iostream& ...
随机推荐
- mysql中列转行
通过group_concat()函数来实现 select group_concat(name) from table group by type select group_concat(name se ...
- Linux笔记2-常用命令
1.简单的命令 cd / 切到根路径 cd .. 回到上一级目录 pwd 显示当前路径 touch newFile 创建文件 mkdir xx 创建目录 mv file1 ...
- pyspider启动错误解决(Python 3.7)
问题一 安装好pyspider之后,在启动的时候,报出上图错误. 原因 async和await从 python3.7 开始已经加入保留关键字中. 参考: What’s New In Python 3. ...
- redis命令行命令
配置文件设置密码认证 修改redis.conf去掉#requirepass foobared前面的#,foobared就是密码,可以进行修改 redis命令设置密码认证config set requi ...
- 如何将网络流转化为内存流 C#
//将获取的文件流转化为内存流 public static MemoryStream ConvertStreamToMemoryStream(Stream stream) { MemoryStream ...
- 如何用CSS定义一个动画?
<style type="text/css"> div{ width:100px;height: 100px; animation: carton 5s; backgr ...
- C# 对象转JSON字符串
对象转JSON字符串 /// <summary> /// 对象转Json字符串 /// </summary> /// <param name="obj" ...
- HTML5:Canvas-绘制图形
到本文的最后,你将学会如何绘制矩形,三角形,直线,圆弧和曲线,变得熟悉这些基本的形状.绘制物体到Canvas前,需掌握路径,我们看看到底怎么做. 栅格 在我们开始画图之前,我们需要了解一下画布栅格(c ...
- P与C
P是排列:与次序有关,P(5.3)=5*4*3 C是组合:与次序无关,C(5.3)=(5*4*3)/(3*2*1)
- springBoot 连接数据库
心得:1.先添加依赖. 2.在application.yml文件中创建mybatis的连接,与项目连接起来. 3.application.yml文件中可以自行配置服务器的端口,配置mybatis的路径 ...