聲明:本文參考鏈接:STL::sort實現。
排序是面試中經常被問及的算法基礎知識點,雖然實際應用中不會直接使用,但是理解這些簡單的算法知識對於更復雜更實用的算法有一定的幫助,畢竟面試總不能問的太過深入,那么這些知識點就顯得很重要了。我們在程序中經常利用sort給序列排序,那么你知道它是什么實現的嗎?
函數聲明
#include <algorithm> template <class RandomAccessIterator> void sort (RandomAccessIterator first, RandomAccessIterator last); template <class RandomAccessIterator, class Compare> void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
來自sort - C++ Reference。STL提供了兩種調用方式,一種是使用默認的 < 操作符比較,一種可以自定義比較函數。可是為什么它通常比我們自己寫的排序要快那么多呢?
實現原理
STL中的sort不是普通的快排,除了對普通的快速排序進行優化,它還結合了插入排序和堆排序。根據不同的數量級別以及不同情況,能自動選用合適的排序方法。當數據量較大時采用快速排序,分段遞歸。一旦分段后的數據量小於某個閥值,為避免遞歸調用帶來過大的額外負荷,便會改用插入排序;而如果遞歸層次過深,有出現最壞情況的傾向,還會改用堆排序。
普通的快速排序
參考我的另一篇隨筆:十大排序算法,有對各個排序算法的分析。其中快速排序的描述如下:
- 從序列中選取排序基准(pivot);
- 對序列進行排序,所有比基准值小的擺放在基准左邊,所有比基准值大的擺在基准的右邊,序列分為左右兩個子序列。稱為分區操作(partition);
- 遞歸,對左右兩個子序列分別進行快速排序。
其中分區操作的方法通常采用兩個迭代器head和tail,head從頭端往尾端移動,tail從尾端往頭端移動,當head遇到大於等於pivot的元素就停下來,tail遇到小於等於pivot的元素也停下來,若head迭代器仍然小於tail迭代器,即兩者沒有交叉,則互換元素,然后繼續進行相同的動作,向中間逼近,直到兩個迭代器交叉,結束一次分割。
快速排序最關鍵的地方在於基准的選擇,最壞的情況發生在分割時產生了一個空的區間,這樣就完全沒有達到分割的效果。STL采用的做法稱為median-of-three,即取整個序列的首、尾、中央三個地方的元素,以其中值作為基准。
內省式排序 Introsort
不當的基准選擇會導致不當的分割,會使快速排序惡化為 O(n^2)。David R.Musser於1996年提出一種混合式排序算法:Introspective Sorting(內省式排序),簡稱IntroSort,其行為大部分與上面所說的median-of-three Quick Sort完全相同,但是當分割行為有惡化為二次方的傾向時,能夠自我偵測,轉而改用堆排序,使效率維持在堆排序的 O(nlgn),又比一開始就使用堆排序來得好。
代碼剖析
sort 函數中最后通過調用 __sort 函數,下面是 __sort 函數的具體實現,默認使用<操作符。
template<typename _RandomAccessIterator, typename _Compare> inline void __sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { if (__first != __last) { std::__introsort_loop(__first, __last, std::__lg(__last - __first) * 2, __comp); std::__final_insertion_sort(__first, __last, __comp); } }
其中的 std::__introsort_loop 便是上面介紹的內省式排序,其第三個參數中所調用的函數 __lg() 便是用來控制分割惡化情況,具體功能類似求lg(n)(取下整),意味着快速排序的遞歸調用最多 2*lg(n) 層。
1.內省式:__introsort_loop
__sort函數首先調用內省式排序,__introsort_loop 函數的實現如下:
/// This is a helper function for the sort routine. template<typename _RandomAccessIterator, typename _Size, typename _Compare> void __introsort_loop(_RandomAccessIterator __first, _RandomAccessIterator __last, _Size __depth_limit, _Compare __comp) { while (__last - __first > int(_S_threshold)) { if (__depth_limit == 0) { std::__partial_sort(__first, __last, __last, __comp); return; } --__depth_limit; _RandomAccessIterator __cut = std::__unguarded_partition_pivot(__first, __last, __comp); std::__introsort_loop(__cut, __last, __depth_limit, __comp); __last = __cut; } }
- 首先判斷元素規模是否大於閥值_S_threshold,_S_threshold是一個常整形的全局變量,值為16,表示若元素規模小於等於16,則結束內省式排序算法,返回sort函數,改用插入排序 __final_insertion_sort。
- 若元素規模大於_S_threshold,則判斷遞歸調用深度是否超過限制。若已經到達最大限制層次的遞歸調用,則改用堆排序。代碼中的 __partial_sort 即用堆排序實現。
- 若沒有超過遞歸調用深度,則調用函數 __unguarded_partition_pivot 對當前元素做一趟快速排序,並返回基准位置。
- 快排之后,再遞歸對右半部分調用內省式排序算法。然后回到while循環,對左半部分進行排序。源碼寫法和我們一般的寫法不同,但原理是一樣的,這是很明顯的尾遞歸優化,需要注意。
2.快速排序:__unguarded_partition_pivot
快速排序函數 __unguarded_partition_pivot 的代碼如下:
/// This is a helper function... template<typename _RandomAccessIterator, typename _Compare> _RandomAccessIterator __unguarded_partition(_RandomAccessIterator __first, _RandomAccessIterator __last, _RandomAccessIterator __pivot, _Compare __comp) { while (true) { while (__comp(__first, __pivot)) ++__first; --__last; while (__comp(__pivot, __last)) --__last; if (!(__first < __last)) return __first; std::iter_swap(__first, __last); ++__first; } } /// This is a helper function... template<typename _RandomAccessIterator, typename _Compare> inline _RandomAccessIterator __unguarded_partition_pivot(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { _RandomAccessIterator __mid = __first + (__last - __first) / 2; std::__move_median_to_first(__first, __first + 1, __mid, __last - 1, __comp); return std::__unguarded_partition(__first + 1, __last, __first, __comp); }
這個代碼比較容易理解,快速排序,並返回樞軸位置。__unguarded_partition()
函數采用的便是上面所講的使用兩個迭代器的方法,將序列分為左右兩個子序列。其中還注意到 __move_median_to_first 函數,就是之前提到的 median-of-three,目的是從頭部、中部、尾部三個數中選出中間值作為“基准”,基准保存在 __first 中,實現代碼如下:
/// Swaps the median value of *__a, *__b and *__c under __comp to *__result template<typename _Iterator, typename _Compare> void __move_median_to_first(_Iterator __result,_Iterator __a, _Iterator __b, _Iterator __c, _Compare __comp) { if (__comp(__a, __b)) { if (__comp(__b, __c)) std::iter_swap(__result, __b); else if (__comp(__a, __c)) std::iter_swap(__result, __c); else std::iter_swap(__result, __a); } else if (__comp(__a, __c)) std::iter_swap(__result, __a); else if (__comp(__b, __c)) std::iter_swap(__result, __c); else std::iter_swap(__result, __b); }
3.堆排序:__partial_sort
之前在 __introsort_loop 函數中看到如果遞歸調用深度是否超過限制,若已經到達最大限制層次的遞歸調用,則改用堆排序。代碼中的 __partial_sort 即用堆排序實現,其部分實現代碼如下(堆排序的代碼特別多):
/// This is a helper function for the sort routines. template<typename _RandomAccessIterator, typename _Compare> void __heap_select(_RandomAccessIterator __first, _RandomAccessIterator __middle, _RandomAccessIterator __last, _Compare __comp) { std::__make_heap(__first, __middle, __comp); for (_RandomAccessIterator __i = __middle; __i < __last; ++__i) if (__comp(__i, __first)) std::__pop_heap(__first, __middle, __i, __comp); } template<typename _RandomAccessIterator, typename _Compare> void __sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { while (__last - __first > 1) { --__last; std::__pop_heap(__first, __last, __last, __comp); } } template<typename _RandomAccessIterator, typename _Compare> inline void __partial_sort(_RandomAccessIterator __first, _RandomAccessIterator __middle, _RandomAccessIterator __last, _Compare __comp) { std::__heap_select(__first, __middle, __last, __comp); std::__sort_heap(__first, __middle, __comp); }
4.插入排序:__final_insertion_sort
經過__introsort_loop排序之后,元素規模小於_S_threshold,最后再次回到 __sort 函數,執行插入排序__final_insertion_sort,其實現代碼如下:
/// This is a helper function for the sort routine. template<typename _RandomAccessIterator, typename _Compare> void __final_insertion_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp) { if (__last - __first > int(_S_threshold)) { std::__insertion_sort(__first, __first + int(_S_threshold), __comp); std::__unguarded_insertion_sort(__first + int(_S_threshold), __last, __comp); } else std::__insertion_sort(__first, __last, __comp); }
結束語
好了,今天就到這里了,相信大家對STL sort
也有了一定的了解,如果發現任何錯誤,歡迎大家批評指正,一起交流!
本文版權歸作者AlvinZH和博客園所有,歡迎轉載和商用,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利.