做者:fengcc 原創做品 轉載請註明出處面試
前幾天阿里電話一面,被問到STL
中sort
函數的實現。之前沒有仔細探究過,聽人說是快速排序,因而回答說用快速排序實現的,但聽電話另外一端面試官的聲音,感受不對勁,知道本身回答錯了。這幾天特地看了一下,在此記錄。算法
#include <algorithm> template< class RandomIt > void sort( RandomIt first, RandomIt last ); template< class RandomIt, class Compare > void sort( RandomIt first, RandomIt last, Compare comp );
使用方法很是簡單,STL
提供了兩種調用方式,一種是使用默認的<
操做符比較,一種能夠自定義比較函數。但是爲何它一般比咱們本身寫的排序要快那麼多呢?dom
原來,STL
中的sort
並不是只是普通的快速排序,除了對普通的快速排序進行優化,它還結合了插入排序和堆排序。根據不一樣的數量級別以及不一樣狀況,能自動選用合適的排序方法。當數據量較大時採用快速排序,分段遞歸。一旦分段後的數據量小於某個閥值,爲避免遞歸調用帶來過大的額外負荷,便會改用插入排序。而若是遞歸層次過深,有出現最壞狀況的傾向,還會改用堆排序。函數
普通快速排序算法能夠敘述以下,假設S表明須要被排序的數據序列:oop
S
中的元素只有0個或1個,結束。S
中的任何一個元素做爲樞軸pivot
。S
分割爲L
、R
兩端,使L
內的元素都小於等於pivot
,R
內的元素都大於等於pivot
。L
、R
遞歸執行上述過程。快速排序最關鍵的地方在於樞軸的選擇,最壞的狀況發生在分割時產生了一個空的區間,這樣就徹底沒有達到分割的效果。STL
採用的作法稱爲median-of-three
,即取整個序列的首、尾、中央三個地方的元素,以其中值做爲樞軸。優化
分割的方法一般採用兩個迭代器head
和tail
,head
從頭端往尾端移動,tail
從尾端往頭端移動,當head
遇到大於等於pivot
的元素就停下來,tail
遇到小於等於pivot
的元素也停下來,若head
迭代器仍然小於tail
迭代器,即二者沒有交叉,則互換元素,而後繼續進行相同的動做,向中間逼近,直到兩個迭代器交叉,結束一次分割。ui
看一張來自維基百科上關於快速排序的動態圖片,幫助理解。
code
不當的樞軸選擇,致使不當的分割,會使快速排序惡化爲 O(n2)。David R.Musser於1996年提出一種混合式排序算法:Introspective Sorting
(內省式排序),簡稱IntroSort
,其行爲大部分與上面所說的median-of-three Quick Sort
徹底相同,可是當分割行爲有惡化爲二次方的傾向時,可以自我偵測,轉而改用堆排序,使效率維持在堆排序的 O(nlgn),又比一開始就使用堆排序來得好。排序
下面是完整的SGI STL sort()
源碼(使用默認<
操做符版)遞歸
template <class _RandomAccessIter> inline void sort(_RandomAccessIter __first, _RandomAccessIter __last) { __STL_REQUIRES(_RandomAccessIter, _Mutable_RandomAccessIterator); __STL_REQUIRES(typename iterator_traits<_RandomAccessIter>::value_type, _LessThanComparable); if (__first != __last) { __introsort_loop(__first, __last, __VALUE_TYPE(__first), __lg(__last - __first) * 2); __final_insertion_sort(__first, __last); } }
其中,__introsort_loop
即是上面介紹的內省式排序,其第三個參數中所調用的函數__lg()
即是用來控制分割惡化狀況,代碼以下:
template <class Size> inline Size __lg(Size n) { Size k; for (k = 0; n > 1; n >>= 1) ++k; return k; }
即求lg(n)
(取下整),意味着快速排序的遞歸調用最多 2*lg(n) 層。
內省式排序算法以下:
template <class _RandomAccessIter, class _Tp, class _Size> void __introsort_loop(_RandomAccessIter __first, _RandomAccessIter __last, _Tp*, _Size __depth_limit) { while (__last - __first > __stl_threshold) { if (__depth_limit == 0) { partial_sort(__first, __last, __last); return; } --__depth_limit; _RandomAccessIter __cut = __unguarded_partition(__first, __last, _Tp(__median(*__first, *(__first + (__last - __first)/2), *(__last - 1)))); __introsort_loop(__cut, __last, (_Tp*) 0, __depth_limit); __last = __cut; } }
__stl_threshold
,__stl_threshold
是一個常整形的全局變量,值爲16,表示若元素規模小於等於16,則結束內省式排序算法,返回sort
函數,改用插入排序。__stl_threshold
,則判斷遞歸調用深度是否超過限制。若已經到達最大限制層次的遞歸調用,則改用堆排序。代碼中的partial_sort
即用堆排序實現。__unguarded_partition()
對當前元素作一趟快速排序,並返回樞軸位置。__unguarded_partition()
函數採用的即是上面所講的使用兩個迭代器的方法,代碼以下:template <class _RandomAccessIter, class _Tp> _RandomAccessIter __unguarded_partition(_RandomAccessIter __first, _RandomAccessIter __last, _Tp __pivot) { while (true) { while (*__first < __pivot) ++__first; --__last; while (__pivot < *__last) --__last; if (!(__first < __last)) return __first; iter_swap(__first, __last); ++__first; } }
遞歸上述過程,直到元素規模小於__stl_threshold
,而後返回sort
函數,對整個元素序列調用一次插入排序,此時序列中的元素已基本有序,因此插入排序也很快。至此,整個sort
函數運行結束。
好了,今天就到這裏了,相信你們對STL sort
也有了必定的瞭解,若是發現任何錯誤,歡迎你們批評指正,一塊兒交流!