STL源碼分析——sort排序

稍微花了一點點時間看了一下老師推薦的博客:http://feihu.me/blog/2014/sgi-std-sort/,看完後無不讚嘆STL追求效率之極致,STL的sort排序算法綜合了三種排序快排,堆排和插入排序,被稱爲Introspective Sort(內省式排序),在算法內部根據自身不一樣的情形來判斷來使用不一樣的算法進行排序,sort算法能夠說綜合了三種排序算法的優勢,追求效率到了極致。ios

 

一開始sort算法有兩部分組成,__introsort_loop和__final_insertion_sort程序員

template <class RandomAccessIterator> inline void sort(RandomAccessIterator first, RandomAccessIterator last) { if (first != last) { __introsort_loop(first, last, value_type(first), __lg(last - first) * 2); //std::sort的最後一步——插入排序。
 __final_insertion_sort(first, last); } }

__introsort_loop便是Introspective Sort內省式排序,__final_insertion_sort是插入排序。首先講內省式排序即sort算法的大頭。算法

template <class RandomAccessIterator, class T, class Size>
void __introsort_loop(RandomAccessIterator first, RandomAccessIterator last, T*,Size depth_limit) { while (last - first > __stl_threshold) { //depth_limit是前面所提到的判斷分割行爲是否有惡化傾向的閾值
        if (depth_limit == 0) { //函數調用partial_sort,它即是堆排序
 partial_sort(first, last, last); return; } --depth_limit; RandomAccessIterator cut = __unguarded_partition(first, last, T(__median(*first, *(first + (last - first) / 2), *(last - 1)))); __introsort_loop(cut, last, value_type(first), depth_limit); last = cut; } }

首先是while語句判斷,last-first > __stl_threshold,__stl_threshold爲最小分段閾值,值爲16,大致上來講,當數據量很大時,Introspective Sort都是採用快速排序,也就是__unguarded_partition,當last-first<16時,再使用遞歸來排序顯然不划算,遞歸的開銷相對來講太大,這時候將退出函數執行__final_insertion_sort插入排序。另外,在遞歸部分,快速排序也作了優化,使用了單邊循環的方式作遞歸,還有一點優化是pivot的取值,__median函數,它取首部、尾部和中部三個元素的中值做爲pivot,而咱們平時直接用首部,中部或者尾部來作pivot,一般咱們並不比較他們的取值,在不少狀況下這將引發遞歸的惡化,depth_limit就是用來判斷分割行爲是否有惡化傾向的閾值,當depth_limit減到0時將採用partial_sort堆排序。再說回單邊循環的方式的遞歸,在用__unguarded_partition快排後取到index作cut,遞歸進入__introsort_loop,區間是[cut, last),下一行又將last賦值成cut即last = cut;單邊遞歸完成後回到開始,又開始__unguarded_partition快排,此時的邊區間就到了[first,cut),構成了一個左右的循環結構,而不是平時寫的快排左右的遞歸結構,STL這樣寫節省了一半的函數遞歸調用,大大減小了開銷(這樣可讀性就降低了,但爲了效率,STL程序員也是拼到了極致)。該函數__introsort_loop退出條件是last-first < __stl_threshold即區域小於等於閾值16,或者超過遞歸深度閾值depth_limit,函數退出後進入到__final_insertion_sort插入排序,(這個時候序列的每一個子序列都有至關程度的排序,但又還沒有徹底排序,使用插入排序再好不過)__final_insertion_sort也不是簡單的插入排序,STL程序員們在簡單的插入排序算法中進一步作了優化,將插入排序又分紅了__unguarded_linear_insert,__linear_insert —— 無邊界做檢查的插入排序和邊界做檢查的插入排序。安全

template <class RandomAccessIterator, class T>
void __unguarded_linear_insert(RandomAccessIterator last, T value) { RandomAccessIterator next = last; --next; while (value < *next) { *last = *next; last = next; --next; } *last = value; } template <class RandomAccessIterator, class T> inline void __linear_insert(RandomAccessIterator first, RandomAccessIterator last, T*) { T value = *last; if (value < *first) { copy_backward(first, last, last + 1); *first = value; } else __unguarded_linear_insert(last, value); }

__unguarded_linear_insert不對邊界做檢查。正由於如此,它必定比下面的__insertion_sort要快。那麼爲何插入排序又能夠分紅無邊界做檢查和邊界做檢查呢。回到sort函數中的__final_insertion_sort函數進行分析。dom

template <class RandomAccessIterator>
void __final_insertion_sort(RandomAccessIterator first, RandomAccessIterator last) { //const int __stl_threshold = 16; 最小分段閾值 //一個帶邊界檢查而另外一個不帶,不帶邊界檢查的__unguarded_insertion_sort更快。
    if (last - first > __stl_threshold) { __insertion_sort(first, first + __stl_threshold); __unguarded_insertion_sort(first + __stl_threshold, last); } else
        //__insertion_sort和__unguarded_insertion_sort有何區別? //有無邊界檢測
 __insertion_sort(first, last); }

咱們發現若是區間大小last-first>16時將採用__insertion_sort和__unguarded_insertion_sort,__insertion_sort有邊界檢測插入排序區間是[first,first+16],__unguarded_insertion_sort無邊界檢測插入排序區間是[first+17,last],last-first<16時直接採用有邊界檢測插入排序。重點在於爲何在區間開始的__stl_threshold個元素以後就能用無邊界檢測插入排序呢,函數

進一步分析,oop

咱們首先只考慮最左邊的子序列,先假設是因爲第一種狀況終止了這個函數,那麼該子區域小於16。
由於使用的是快速排序,左邊區間的全部數據必定比右邊小,能夠推斷出最小值必定在該小於16的子區域內。
假設函數是第二種狀況下終止,那麼對於最左邊的區間,因爲遞歸深度過深,所以該區間會調用堆排序,因此這段區間的最小值必定位於最左端。
加上前面的分析:左邊區間全部的數據必定比右邊小,那麼該區間內最左邊的數據裏必定有整個序列的最小值。
所以,不管是哪一種狀況,均可以保證起始的16個元素中必定有最小值。
如此便可以使用__insertion_sort對前16個元素進行排序,接着用__unguarded_insertion_sort毫無顧忌的在不考慮邊界的狀況下對剩於的區間進行更快速的排序。學習

 

寫到這裏,sort算法差很少分析完了,最後說一下感想,寫代碼要看做是雕琢一件藝術品,只有通過不斷的打磨,才能在穩定性、安全性、通用性和效率上經歷住考驗,永遠不要以爲本身寫的代碼已經到了完美了,要不斷進行修改和優化,並且這個過程是不斷進行的,只有不斷追求卓越,才能到達頂峯。STL庫積蓄了C++程序員們數十年的心血,裏面的種種細節都值得咱們去學習,學海無涯,書山有路,最後祝你們早日達成本身的理想。優化

 

#include <iostream>

const int __stl_threshold = 16; template <class RandomAccessIterator, class T> RandomAccessIterator __unguarded_partition(RandomAccessIterator first, RandomAccessIterator last, T pivot) { while (true) { while (*first < pivot) ++first; --last; while (pivot < *last) --last; if (!(first < last)) return first; iter_swap(first, last); ++first; } } template <class RandomAccessIterator, class T, class Compare>
void __partial_sort(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, T*, Compare comp) { make_heap(first, middle, comp); for (RandomAccessIterator i = middle; i < last; ++i) if (comp(*i, *first)) __pop_heap(first, middle, i, T(*i), comp, distance_type(first)); sort_heap(first, middle, comp); } //partial_sort,堆排序
template <class RandomAccessIterator, class Compare> inline void partial_sort(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, Compare comp) { __partial_sort(first, middle, last, value_type(first), comp); } //Introspective Sorting(內省式排序) /* 該函數只有兩種狀況下可能返回, 一是區域小於等於閾值16;二是超過遞歸深度閾值。 咱們如今只考慮最左邊的子序列,先假設是因爲第一種狀況終止了這個函數,那麼該子區域小於16。 再根據前面的結論:左邊區間的全部數據必定比右邊小,能夠推斷出最小值必定在該小於16的子區域內。 假設函數是第二種狀況下終止,那麼對於最左邊的區間,因爲遞歸深度過深,所以該區間會調用堆排序,因此這段區間的最小值必定位於最左端。 再加上前面的結論:左邊區間全部的數據必定比右邊小,那麼該區間內最左邊的數據必定是整個序列的最小值。 所以,不管是哪一種狀況,均可以保證起始的16個元素中必定有最小值。 如此便可以使用__insertion_sort對前16個元素進行排序,接着用__unguarded_insertion_sort毫無顧忌的在不考慮邊界的狀況下對剩於的區間進行更快速的排序。 */ template <class RandomAccessIterator, class T, class Size>
void __introsort_loop(RandomAccessIterator first, RandomAccessIterator last, T*,Size depth_limit) { //const int __stl_threshold = 16; 最小分段閾值 //當數據長度小於該閾值時,再使用遞歸來排序顯然不划算,遞歸的開銷相對來講太大。 //而此時整個區間內部有多個元素個數少於16的子序列, //每一個子序列都有至關程度的排序,但又還沒有徹底排序,過多的遞歸調用是不可取的。 //而這種狀況恰好插入排序最拿手,它的效率可以達到O(N)。 //所以這裏停止快速排序,sort會接着調用外部的__final_insertion_sort x行
    while (last - first > __stl_threshold) { //depth_limit是前面所提到的判斷分割行爲是否有惡化傾向的閾值
        if (depth_limit == 0) { //函數調用partial_sort,它即是堆排序
 partial_sort(first, last, last); return; } --depth_limit; //__unguarded_partition,這其實就是咱們日常所使用的快速排序主體部分,用於根據pivot將區間分割爲兩個子序列。 //__median函數,它的做用是取首部、尾部和中部三個元素的中值做爲pivot。 //咱們以前學到的快速排序都是選擇首部、尾部或者中間位置的元素做爲pivot,並不會比較它們的值,在不少狀況下這將引發遞歸的惡化。如今這裏採用的中值法能夠在絕大部分情形下優於原來的選擇。
        RandomAccessIterator cut = __unguarded_partition(first, last, T(__median(*first, *(first + (last - first) / 2), *(last - 1)))); //遞歸結構
        /* 能夠看出它是一個遞歸函數,由於咱們說過,Introspective Sort在數據量很大的時候採用的是正常的快速排序, 所以除了處理惡化狀況之外,它的結構應該和快速排序一致。 但仔細看代碼,先無論循環條件和if語句(它們即是處理惡化狀況所用),循環的後半部分是用來遞歸調用快速排序。 */
        //__introsort_loop中只有對右邊子序列進行遞歸調用是否是? //左邊的遞歸不見了。的確,這裏的寫法可讀性相對來講比較差,可是仔細一分析發現是有它的道理的,它並非沒有管左子序列。 //注意看,在分割原始區域以後,對右子序列進行了遞歸,接下來的last = cut將終點位置調整到了分割點,那麼此時的[first, last)區間就是左子序列了。 //又由於這是一個循環結構,那麼在下一次的循環中,左子序列便獲得了處理。只是並未以遞歸來調用! //二者區別就在於STL節省了接近一半的函數調用,因爲每次的函數調用有必定的開銷,所以對於數據量很是龐大時,這一半的函數調用可能可以省下至關可觀的時間。
 __introsort_loop(cut, last, value_type(first), depth_limit); last = cut; } } //__unguarded_linear_insert不對邊界做檢查。正由於如此,它必定比下面的__insertion_sort要快。
template <class RandomAccessIterator, class T>
void __unguarded_linear_insert(RandomAccessIterator last, T value) { RandomAccessIterator next = last; --next; while (value < *next) { *last = *next; last = next; --next; } *last = value; } template <class RandomAccessIterator, class T> inline void __linear_insert(RandomAccessIterator first, RandomAccessIterator last, T*) { T value = *last; if (value < *first) { copy_backward(first, last, last + 1); *first = value; } else __unguarded_linear_insert(last, value); } template <class RandomAccessIterator>
void __insertion_sort(RandomAccessIterator first, RandomAccessIterator last) { if (first == last) return; for (RandomAccessIterator i = first + 1; i != last; ++i) __linear_insert(first, i, value_type(first)); } //能夠忽略掉這層aux函數的包裝,它只是爲了得到迭代器所指向的類型,其實這兩個函數能夠合併爲一個。
template <class RandomAccessIterator, class T>
void __unguarded_insertion_sort_aux(RandomAccessIterator first, RandomAccessIterator last, T*) { for (RandomAccessIterator i = first; i != last; ++i) __unguarded_linear_insert(i, T(*i)); } template <class RandomAccessIterator> inline void __unguarded_insertion_sort(RandomAccessIterator first, RandomAccessIterator last) { __unguarded_insertion_sort_aux(first, last, value_type(first)); } template <class RandomAccessIterator>
void __final_insertion_sort(RandomAccessIterator first, RandomAccessIterator last) { //const int __stl_threshold = 16; 最小分段閾值 //一個帶邊界檢查而另外一個不帶,不帶邊界檢查的__unguarded_insertion_sort更快。
    if (last - first > __stl_threshold) { __insertion_sort(first, first + __stl_threshold); __unguarded_insertion_sort(first + __stl_threshold, last); } else
        //__insertion_sort和__unguarded_insertion_sort有何區別? //有無邊界檢測
 __insertion_sort(first, last); } template <class RandomAccessIterator> inline void sort(RandomAccessIterator first, RandomAccessIterator last) { if (first != last) { __introsort_loop(first, last, value_type(first), __lg(last - first) * 2); //std::sort的最後一步——插入排序。
 __final_insertion_sort(first, last); } }
相關文章
相關標籤/搜索