SGI STL Sort演算法
Sort
stl所提供的各式各樣的演算法中,sort()是最複雜龐大的一個。這個演算法接受兩個隨機存取迭代器,然後將區間內的所有元素以漸增方式由小到大重新排列。還有個版本則允許使用者指定一個仿函式,作為排序標準。
stl中的所有關係型容器都擁有自動排序功能,所以不需要sort演算法。序列式容器中的stack,deque和priority_queue都有特別的入口,不允許使用者對元素排序。剩下的vector,deque和list,前兩者的迭代器屬於RandomAccessIterators,適合用sort演算法,list的迭代器則屬於BidrectionalIterators,不適合用sort演算法。
STL的sort演算法,資料量大時採用Quick Sort(快速排序),分段遞迴排序,一旦分段後的資料量小於某個門檻,為避免Quick Sort的遞迴呼叫帶來過大的額外負荷,就改用Insertion Sort(插入排序)。如果遞迴層次過深,還會改用Heap Sort(堆排序)。
STL中的Insertion Sort
預設版本(版本二允許指定一個仿函式作為倆元素的比較函式)
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)); //[first,i)形成一個子區間 } //輔助函式 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, class T> void __unguarded_linear_insert(RandomAccessIterator last, T value) { RandomAccessIterator next = last; --next; //一旦不再出現逆序對,迴圈就可以結束了 while (value < *next) { //逆序對存在,需要進行調整 *last = *next; last = next; --next; } *last = value; //value的正確落腳處 }
STL中的Quick Sort
Quick Sort是目前已知最快的排序法,平均複雜度O(NlogN)。早期的STL sort演算法都採用Quick Sort,SGI STL已改用IntroSort。
Median-of-Three(三點中值)
快速排序首先要選擇樞紐,為了避免“元素當初輸入時不夠隨機”所帶來的惡化效應,理想的方法是取整個序列的頭、尾、中央三個位置的元素,以其中值為樞紐。
template <class T> inline const T& __median(const T& a, const T& b, const T& c) { if (a < b) if (b < c) return b; else if (a < c) return c; else return a; else if (a < c) return a; else if (b < c) return c; else return b; }
Partitionining(分割)
分割方法有很多,以下敘述既簡單又有良好成效的做法。令first向尾移動,last向頭移動。當*first大於或等於pivot時停下來(應該往後放),當*last小於或等於pivot時也停下來(應該往前放),然後檢驗兩個迭代器是否交錯(first在左,last在右則不交錯)。未交錯則元素互相交換,然後各自調整一個位置,再繼續相同行為。若交錯,則以此時first為軸將序列分為左右兩半,左邊值都小於或等於pivot,右邊都大於等於pivot。
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;
}
}
introsort
不當的樞軸選擇,導致不當的分割,導致Quick Sort惡化為O(N^2)。David R. Musser於1996年提出一種混合式排序演算法,Introspective Sorting。其行為在大部分情況下幾乎與 median-of-3 Quick Sort完全相同。但是當分割行為(partitioning)有惡化為二次行為傾向時,能自我偵測,轉而改用Heap Sort,使效率維持在O(NlogN),又比一開始就使用Heap Sort來得好。下面就是SGI STL原始碼中堆IntroSort的實現。
SGI STL 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);
//經過上面的__introsort_loop已經排的差不多了,下面使用插入排序效率較高
__final_insertion_sort(first, last);
}
}
其中的__lg()用來控制分割惡化的情況:
找出2^k <= n的最大值k。比如:n=20,得k=4。
template <class Size>
inline Size __lg(Size n) {
Size k;
for (k = 0; n > 1; n >>= 1) ++k;
return k;
}
當元素個數為40時,__introsoft_loop()的最後一個引數將是5*2,意思是最多允許分割10層。
const int __stl_threshold = 16;
//本函式內的許多迭代器運算操作,都只適用於RandomAccess Iterators
emplate <class RandomAccessIterator, class T, class Size>
void __introsort_loop(RandomAccessIterator first,
RandomAccessIterator last, T*,
Size depth_limit) {
while (last - first > __stl_threshold) {
//depth_limit==0說明分割惡化,改用heapsort
if (depth_limit == 0) {
//進行堆排序
partial_sort(first, last, last);
return;
}
--depth_limit;
//三點中值決定函式,選擇一個較好的樞紐作為分割點,分割點將落在迭代器cur身上
RandomAccessIterator cut = __unguarded_partition
(first, last, T(__median(*first, *(first + (last - first)/2),
*(last - 1))));
//堆右半段遞迴進行sort
__introsort_loop(cut, last, value_type(first), depth_limit);
last = cut;
//現在回到while迴圈,準備對左半段遞迴進行sort
}
}
當__introsort_loop()結束,[first,last)內有多個“元素個數少於16”的子序列,每個子序列都經過了相當程度的排序,但尚未完全排序(因為元素個數一旦小於__stl_threshold,就被終止進一步的排序操作了)。然後進入函式__final_insertion_sort()。
判斷元素個數是否大於16。如果答案為否,就呼叫__insertion_sort()加以處理。否則就將[first,last)分割為長度為16的一段子序列和另一端剩餘子序列,再針對兩個子序列分別呼叫__insertion_sort()和__unguarded_insertion_sort()。
template <class RandomAccessIterator>
void __final_insertion_sort(RandomAccessIterator first,
RandomAccessIterator last) {
if (last - first > __stl_threshold) {
__insertion_sort(first, first + __stl_threshold);
__unguarded_insertion_sort(first + __stl_threshold, last);
}
else
__insertion_sort(first, last);
}
template <class RandomAccessIterator>
inline void __unguarded_insertion_sort(RandomAccessIterator first,
RandomAccessIterator last) {
__unguarded_insertion_sort_aux(first, last, value_type(first));
}
template <class RandomAccessIterator, class T, class Compare>
void __unguarded_insertion_sort_aux(RandomAccessIterator first,
RandomAccessIterator last,
T*, Compare comp) {
for (RandomAccessIterator i = first; i != last; ++i)
//程式碼在前面的快速排序中
__unguarded_linear_insert(i, T(*i), comp);
}