SGI STL Sort算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wk_bjut_edu_cn/article/details/83548052

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

猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/83548052