1. 程式人生 > >SGI STL Sort演算法

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);
}