为什么不要自己乱造轮子: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方法的实现的主要内容,如果未能解决你的问题,请参考以下文章

科普:std::sort干了什么

来仿一仿retrofit

来仿一仿retrofit

C++ - 使用 std::sort 对结构向量进行排序导致读取访问冲突

「从零开始造 RPC 轮子系列」01 我为什么要去造一个轮子?

「从零开始造 RPC 轮子系列」01 我为什么要去造一个轮子?