SGI STL sort()原始碼分析
首先將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
排序稱為內省式排序,introsort是David R.Musser於1996年提出一種混合式排序演算法:Introspective Sorting
(內省式排序),簡稱IntroSort
,其行為大部分與上面所說的median-of-three Quick
Sort
完全相同,但是當分割行為有惡化為二次方的傾向時,能夠自我偵測,轉而改用堆排序,使效率維持在堆排序的 O(nlgn),又比一開始就使用堆排序來得好。
__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; } }
1.首先判斷元素規模是否大於閥值__stl_threshold
,__stl_threshold
是一個常整形的全域性變數,值為16,表示若元素規模小於等於16,則結束內省式排序演算法,返回sort
函式,改用插入排序。
2.若元素規模大於__stl_threshold
,則限制。若已經到達最大限制層次的遞迴呼叫,則改用堆排序。程式碼中的partial_sort
即用堆排序實現。
3.若沒有超過遞迴呼叫深度,則呼叫函式__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;
}
}
4.經過一趟快速排序後,再遞迴對右半部分呼叫內省式排序演算法。然後回到while迴圈,對左半部分進行排序。原始碼寫法和我們一般的寫法不同,但原理是一樣的,需要注意。
遞迴上述過程,直到元素規模小於
__stl_threshold
,然後返回sort
函式,對整個元素序列呼叫一次插入排序,此時序列中的元素已基本有序,所以插入排序也很快。至此,整個sort
函式執行結束。
備註;
1、STL
中的sort
並非只是普通的快速排序,除了對普通的快速排序進行優化,它還結合了插入排序和堆排序。根據不同的數量級別以及不同情況,能自動選用合適的排序方法。當資料量較大時採用快速排序,分段遞迴。一旦分段後的資料量小於某個閥值,為避免遞迴呼叫帶來過大的額外負荷,便會改用插入排序。而如果遞迴層次過深,有出現最壞情況的傾向,還會改用堆排序。
2、快速排序最關鍵的地方在於樞軸的選擇,最壞的情況發生在分割時產生了一個空的區間,這樣就完全沒有達到分割的效果。STL採用的做法稱為median-of-three,即取整個序列的首、尾、中央三個地方的元素,以其中值作為樞軸。
分割的方法通常採用兩個迭代器head
和tail
,head
從頭端往尾端移動,tail
從尾端往頭端移動,當head
遇到大於等於pivot
的元素就停下來,tail
遇到小於等於pivot
的元素也停下來,若head
迭代器仍然小於tail
迭代器,即兩者沒有交叉,則互換元素,然後繼續進行相同的動作,向中間逼近,直到兩個迭代器交叉,結束一次分割。