STL裏sort算法用的是什麼排序算法

本文轉載:https://blog.csdn.net/qq_35440678/article/details/80147601,感謝博主的分享!

前言

當你第一眼看到這道面試題是不是心裏在暗喜,一問算法題就比問排序算法,一問排序算法就問快速排序。
如果你回答:

STL裏的sort算法肯定用的是快速排序啊?難不成還是冒泡排序麼?

如果你只是回答快速排序,那麼恭喜你只答對了33.333%,離正確答案還差一大截。

回答完,接着會引來一堆問題轟炸:

  • 數據量大和數據量小都適合用快速排序嗎?
  • 快速排序的時間複雜度不是穩定的nlogn,最壞情況會變成n^2,怎麼解決複雜度惡化問題?
  • 快速排序遞歸實現時,怎麼解決遞歸層次過深的問題?
  • 遞歸過深會引發什麼問題?
  • 怎麼控制遞歸深度?如果達到遞歸深度了還沒排完序怎麼辦?

首先,回答用到哪種排序算法,正確答案是:

毫無疑問是用到了快速排序,但不僅僅只用了快速排序,還結合了插入排序和堆排序。

是不是很驚喜,很意外?

爲什麼?直接看STL源碼實現,來源於侯捷老師翻譯的鼎鼎大名的《STL源碼剖析》關於sort算法實現的細節,實現細節有很多精彩的地方。

並非所有容器都使用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);
    }

爲什麼用插入排序?因爲插入排序在面對“幾近排序”的序列時,表現更好。

結束語

最好的理解方式還是看書再結合源碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章