为什么不要自己乱造轮子:std::sort方法的实现
Posted jo3yzhu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么不要自己乱造轮子:std::sort方法的实现相关的知识,希望对你有一定的参考价值。
引言
关于标准库的sort的实现,各种贴子文章看得人眼花缭乱,还是看code吧。本文代码取自Ubuntu 18.04自带的GNU STL。(个人觉得MSVC版本的STL可阅读性并不是那么好)
std::sort
std::sort经过一些必要的检查后调用了std::__sort并且指定了默认的比较器,所以sort默认是从小到大排列。
template<typename _RandomAccessIterator>
inline void
sort(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
// concept requirements
__glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
_RandomAccessIterator>)
__glibcxx_function_requires(_LessThanComparableConcept<
typename iterator_traits<_RandomAccessIterator>::value_type>)
__glibcxx_requires_valid_range(__first, __last);
__glibcxx_requires_irreflexive(__first, __last);
std::__sort(__first, __last, __gnu_cxx::__ops::__iter_less_iter());
}
std::__sort
std::__sort才是真正的排序实现,可以看到sort采用了introsort(内省排序,快排和堆排序的结合)和insertionsort(插入排序)结合的方式来实现高效排序。
introsort先将数组排成大致有序的样子,再交给insertionsort处理。
注意:introsort还需要额外传入一个__depth_limit作为在函数内部中什么时候使用堆排序,什么时候使用快速排序的依据。
template<typename _RandomAccessIterator, typename _Compare>
inline void
__sort(_RandomAccessIterator __first, _RandomAccessIterator __last,
_Compare __comp)
{
if (__first != __last)
{
std::__introsort_loop(__first, __last,
std::__lg(__last - __first) * 2, // std::__lg(n)*2就是取n以2位底的对数(当然舍入过了),假如数组长度是10,那么这里传入log2(10)=3
__comp);
std::__final_insertion_sort(__first, __last, __comp);
}
}
下面分别看看introsort和insertsort在STL中的实现。
std::__introsort_loop
注意:如果数组的长度_大于S_threshold才使用nlog(n)的堆排序和快排方法,因为他们的常数项比起插入排序实在了大了点。
在递归深度到了__depth_limit之后,就转而使用堆排序去排序子区间。但是由于这个时候并没有递归到树的最底部,所以此时的数组只是近似有序/局部有序
另外,__introsort_loop中还采用循环和递归结合的方式,让普通的快排的栈空间节省了一半。
enum { _S_threshold = 16 };
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); // 根据pivot把区间分为两半,根据源码阅读结果,是用第一个元素
std::__introsort_loop(__cut, __last, __depth_limit, __comp); // 对右边进行排序
__last = __cut; // 再对左边进行排序
}
}
std::__final_insertion_sort
进入这个函数的情况有两种,可能introsort没有使用(区间太短),也有可能introsort已经有效的执行过了。
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);
}
以上是关于为什么不要自己乱造轮子:std::sort方法的实现的主要内容,如果未能解决你的问题,请参考以下文章
C++ - 使用 std::sort 对结构向量进行排序导致读取访问冲突