科普: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& ...
随机推荐
- Java数组相关算法一
一.数组反转 1.方法一:创建新数组 int[] arr = {6,29,0,4,3}; int[] arr2 = new int[arr.length]; for (int i = 0; i < ...
- 在js里的ejs模板引擎使用
1.首先需要在页面里引用ejs.min.js. 2.将你的模板使用ejs编写,并存成后缀名.stmpl;(可能需要在打包工具里做些处理) 3.在js里使用require引入xxx.stmpl: con ...
- 最小生成树(prim和Kruskal操!!SB题)
Arctic Network Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 30571 Accepted: 9220 D ...
- Node.js 中监听 redis key 过期事件
It is in fact possible to listen to the “expired” type keyevent notification using a subscribed clie ...
- OS库的使用
Python中有关OS库的使用 路径操作 os.path.abspath(path) 返回path在当前系统中的绝对路径 os.path.normpath(path) 归一化path的表示形式,统一用 ...
- 115-基于TI TMS320DM6467T Camera Link 机器视觉 智能图像分析平台
基于TI TMS320DM6467无操作系统Camera Link智能图像分析平台 1.板卡概述 该板卡是我公司推出的一款具有超高可靠性.效率最大化.无操作系统的智能视频处理卡,是机器视觉开发上的首选 ...
- Spark- Spark从SFTP中读取zip压缩文件数据做计算
我们遇到个特别的需求,一个数据接入的流程跑的太慢,需要升级为用大数据方式去处理,提高效率. 数据: 数据csv文件用Zip 压缩后放置在SFTP中 数据来源: SFTP 数据操作: 文件和它的压缩包一 ...
- Redis服务器中有75%受到恶意软件感染
尽管由于配置错误的服务器和应用程序而导致新的网络攻击不断出现,但人们仍然忽略安全警告. 近两个月前,中国知名黑客组织东方联盟研究人员警告说,一项针对开放Redis服务器的大规模恶意软件活动现在已经发展 ...
- Servlet学习request对象总结
一.servletContext对象和request对象的比较 ServletContext 何时创建:服务器启动 何时销毁:服务器关闭 域的作用范围:整个web应用 request 何时创建:访问时 ...
- Java中的heap和stack
heap和stack Java的内存分为两类,一类是栈内存,一类是堆内存. 栈内存:是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配 ...