1. 程式人生 > >STL的內觀排序(introsort)演算法學習筆記

STL的內觀排序(introsort)演算法學習筆記

STL(Standard Template Library)的演算法據說是經過精心優化的。那麼在它的排序演算法方面做了哪些優化呢?


自從快速排序演算法出世以後,從平均效能上來說,除了在資料量極少(<=20)的的情況下其效能不如插入排序外,快速演算法的效能起碼是其他同階演算法的2到3倍,這也已經是教科書裡不爭的事實。


一個最簡單的混合演算法就是在資料量少的時候(n<20),演算法轉入插入排序,而其它時候則仍然採用快速排序,比如


void quicksort(_RandomIterator __start, _RandomIterator __last)
{
      while (__last - __first > __stl_threshold) {
            _RandomIterator __pivot= partition(__first, __last, mean(*__first, *__last, *(__first + (__last-__first)/2));
            quicksort(__first, __pivot);
            __first = __pivot;
      }
      __insert_sort(__first, __last);
}

這裡有一個選擇,就是什麼時候做插入排序:上面的演算法是每次細分到資料量小於閾值就轉入插入排序;另外一種演算法是一旦細分到資料長度小於閾值就退出,最後彙總的時候再來一次總的插入排序。應該說這兩種演算法沒有很大的區別,但是STL使用的是後者。原因最後再說。


STL真正出彩的地方是對快速排序演算法的補充。快速 排序的特點是平均效能好,能達到O(NlgN)的效能,缺點是對於最壞情況效能會下降到O(N^2)。STL對此做的補充是引入一個遞迴計數,當遞迴深度超過一定閾值(STL設定的閾值是2lgN),則演算法轉入一個較慢的但是最壞情況也是O(NlgN)的演算法,比如堆排序(STL把堆排序推廣為partial_sort也就是部分排序)。這一演算法監控自身的遞迴深度,具有一定的內觀性,被稱為內觀排序(introsort--introspective sort),實際上是快速排序法的變種,是一種混合演算法。在最壞情況下能近似達到O(NlgN)的效能。實際上在最壞情況下比堆排序要差點,但是比快速排序要好得多。而其平均效能和快速排序差不多。其演算法如下:


void introsort_loop(RandomIterator __first, RandomIterator __last, int m)
{
while (__last - __first > __stl_threshold) {
   if (0==m) {
    partial_sort(__first, __last, __last);
    return;
   }
   RandomIterator __pivot = mean(*__first, *__last, *(__first+(__last-__first)/2));
   introsort__loop(__first, __pivot);
   __first = __pivot+1;
}
}
void introsort(RandomIterator __first, RandomIterator __last)
{
introsort_loop(__first, __last, __lg(__last-__first)*2);
__final_insert_sort(__first, __last);
}


STL在__final_inser_sort中玩了一個小小的加速trick。其演算法如下:

void __final_insert_sort(__first, __last)
{
if (__last - __first < __stl_threshold)
   __insert_sort(__first, __last);
else {
   __insert_sort(__first, __first+__stl_threshold);
   __unguarded_insert_sort(__first+__std_threshold+1, __last);
}
}

我當時不太明白為什麼插入演算法還要如此,後來自己嘗試優化插入演算法的時候才發現在__unguarded_insert_sort的迴圈中少了一個邊界測試條件,這樣邊界測試條件從兩個降為一個。原因就是經過“粗略的”快速排序後,最小元素已經能確定就在前__stl_threshold個元素中,於是基於位置的邊界條件就可以去掉。具體參看插入排序的演算法。不再贅述。