stl中的sort函数,你真的了解吗


前言

stl中为部分容器准备了sort方法进行排序,但你真的了解他吗?
首先,sort方法使用了哪种算法来进行排序?
如果你回答:

STL里的sort算法肯定用的是快速排序啊?难不成还是冒泡排序么?

如果你只是回答快速排序,那么恭喜你只答对了33.333%,离正确答案还差一大截。

因为还有一大串的问题:

数据量大和数据量小都适合用快速排序吗?
快速排序的时间复杂度不是稳定的nlogn,最坏情况会变成n^2,怎么解决复杂度恶化问题?
快速排序递归实现时,怎么解决递归层次过深的问题?
递归过深会引发什么问题? 怎么控制递归深度?如果达到递归深度了还没排完序怎么办?

用到哪种排序算法,正确答案是:

毫无疑问是用到了快速排序,但不仅仅只用了快速排序,还结合了插入排序和堆排序。

然而并非所有容器都使用sort算法
既然问的是STL的sort算法实现,那么先确认一个问题,哪些STL容器需要用到sort算法?
首先,关系型容器拥有自动排序功能,因为底层采用RB-Tree,所以不需要用到sort算法。
其次,序列式容器中的stack、queue和priority-queue都有特定的出入口,不允许用户对元素排序。
剩下的vector、deque,适用sort算法。

实现逻辑

STL的sort算法,数据量大时采用QuickSort快排算法,分段归并排序。一旦分段后的数据量小于某个门槛(16),为避免QuickSort快排的递归调用带来过大的额外负荷,就改用Insertion
Sort插入排序。如果递归层次过深,还会改用HeapSort堆排序。

结合快速排序-插入排序-堆排序 三种排序算法。


算法具体实现

具体代码
源文件:https://gcc.gnu.org/onlinedocs/libstdc++/libstdc+±html-USERS-4.4/a01347.html

template<typename _RandomAccessIterator>
    inline void
    sort(_RandomAccessIterator __first, _RandomAccessIterator __last)
    {
      typedef typename iterator_traits<_RandomAccessIterator>::value_type
        _ValueType;
 
      // concept requirements
      __glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
            _RandomAccessIterator>)
      __glibcxx_function_requires(_LessThanComparableConcept<_ValueType>)
      __glibcxx_requires_valid_range(__first, __last);
 
      if (__first != __last)
        {
        //快速排序+插入排序
          std::__introsort_loop(__first, __last,
                                std::__lg(__last - __first) * 2);
        //插入排序
          std::__final_insertion_sort(__first, __last);
        }
    }

其中__lg函数是计算递归深度,用来控制分割恶化,当递归深度达到该值改用堆排序,因为堆排序是时间复杂度恒定为nlogn:

template<typename _Size>
    inline _Size
    __lg(_Size __n)
    {
      _Size __k;
      for (__k = 0; __n != 1; __n >>= 1)
        ++__k;
      return __k;
    }

先来看,__introsort_loop 快排实现部分:对于区间小于16的采用快速排序,如果递归深度恶化改用堆排序。

template<typename _RandomAccessIterator, typename _Size>
    void
    __introsort_loop(_RandomAccessIterator __first,
                     _RandomAccessIterator __last,
                     _Size __depth_limit)
    {
      typedef typename iterator_traits<_RandomAccessIterator>::value_type
        _ValueType;
    //_S_threshold=16,每个区间必须大于16才递归
      while (__last - __first > int(_S_threshold))
        {
        //达到指定递归深度,改用堆排序
          if (__depth_limit == 0)
            {
              std::partial_sort(__first, __last, __last);
              return;
            }
          --__depth_limit;
          _RandomAccessIterator __cut =
            std::__unguarded_partition(__first, __last,
                                       _ValueType(std::__median(*__first,
                                                                *(__first
                                                                  + (__last
                                                                     - __first)
                                                                  / 2),
                                                                *(__last
                                                                  - 1))));
          std::__introsort_loop(__cut, __last, __depth_limit);
          __last = __cut;
        }
    }

再来看插入排序部分:

template<typename _RandomAccessIterator>
    void
    __final_insertion_sort(_RandomAccessIterator __first,
                           _RandomAccessIterator __last)
    {
      if (__last - __first > int(_S_threshold))
        {
        //先排前16个
          std::__insertion_sort(__first, __first + int(_S_threshold));
        //后面元素遍历插入到前面有序的正确位置 
         std::__unguarded_insertion_sort(__first + int(_S_threshold), __last);
        }
      else
        std::__insertion_sort(__first, __last);
    }

为什么用插入排序?因为插入排序在面对“几近排序”的序列时,表现更好。

总结

一个简单的sort背后也有开发者仔细的考虑,充分的了解函数有助于我们更好地使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值