科普: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& ...
随机推荐
- 字符串类——KMP算法的应用
1,字符串类中的新功能(本文代码已集成到字符串类——字符串类的创建(上)中,这里讲述函数实现原理): 2,子串查找(KMP 算法直接运用): 1,int indexOf(const char* s) ...
- Matplotlib基础使用
matplotlib 一.Matplotlib基础知识 Matplotlib中的基本图表包括的元素 x轴和y轴 axis 水平和垂直的轴线 x轴和y轴刻度 tick 刻度标示坐标轴的分隔,包括最小刻度 ...
- centos7 nginx完整支持thinkphp pathinfo模式开启方法
thinkphp运行在linux+nginx,如何开启pathinfo模式,我是完整测试过了,没问题的,在thinkphp配置文件 开启 'URL_MODEL' => 1, 1代 ...
- WEB应用安全解决方案测试验证
WEB应用安全解决方案测试报告 --- By jiang.jx at 2017-08-11 WEB应用安全解决方案.docx 链接:https://share.weiyun.com/068b05467 ...
- JVM(9)之 年轻代收集器
开发十年,就只剩下这套架构体系了! >>> 继续上一篇博文所讲的,STW即GC时候的停顿时间,他会暂停我们程序中的所有线程.如果STW所用的时间长而且次数多的话,那么我们整个系统 ...
- JavaFX程序初次运行创建数据库并执行建表SQL
在我的第一个JavaFX程序完成安装的时候才突然发现,不能要用这个软件还要手动执行Sql来建表吧? 于是我的想法是在Main程序中执行时检测数据库连接状况,如果没有检测到数据库或者连接异常,那么出现错 ...
- R语言数据类型与数据结构
一.数据类型 5种 1.character 字符 2.numeric 数值 3.integer 整数 一般数字的存储会默认为数值类型,如果要强调是整数,需要在变量值后面加上 L. x <- 5L ...
- canvas 图片反色
代码实例: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <tit ...
- BZOJ2756 [SCOI2012]奇怪的游戏 最大流
好久没有写博客了.不过这个博客也没有多少人看 最近在写网络流,为了加深理解,来写一两篇题解. 对整个棋盘进行黑白染色以后可以发现,一次操作就是让二分图的两个点的值分别 \(+1\). 这样,我们就可以 ...
- python+django+pycharm 环境配置 (window7)
一.python环境配置 登录python官网,下载windows版的python,本项目使用32位的python2.7.6,下载地址: http://www.python.org/ftp/pytho ...