STL sort 函数实现详解

refer:点击打开原文


函数声明

1
2
3
4
5
6
7
#include <algorithm>
  
template < class  RandomIt >
void  sort( RandomIt first, RandomIt last );
  
template < class  RandomIt, class  Compare >
void  sort( RandomIt first, RandomIt last, Compare comp );

使用方法非常简单,STL提供了两种调用方式,一种是使用默认的<操作符比较,一种可以自定义比较函数。可是为什么它通常比我们自己写的排序要快那么多呢?


实现原理

原来,STL中的sort并非只是普通的快速排序,除了对普通的快速排序进行优化,它还结合了插入排序堆排序。根据不同的数量级别以及不同情况,能自动选用合适的排序方法。当数据量较大时采用快速排序,分段递归。一旦分段后的数据量小于某个阀值,为避免递归调用带来过大的额外负荷,便会改用插入排序。而如果递归层次过深,有出现最坏情况的倾向,还会改用堆排序。

普通的快速排序

普通快速排序算法可以叙述如下,假设S代表需要被排序的数据序列:

  1. 如果S中的元素只有0个或1个,结束。
  2. S中的任何一个元素作为枢轴pivot
  3. S分割为LR两端,使L内的元素都小于等于pivotR内的元素都大于等于pivot
  4. LR递归执行上述过程。

快速排序最关键的地方在于枢轴的选择,最坏的情况发生在分割时产生了一个空的区间,这样就完全没有达到分割的效果。STL采用的做法称为median-of-three,即取整个序列的首、尾、中央三个地方的元素,以其中值作为枢轴。

分割的方法通常采用两个迭代器headtailhead从头端往尾端移动,tail从尾端往头端移动,当head遇到大于等于pivot的元素就停下来,tail遇到小于等于pivot的元素也停下来,若head迭代器仍然小于tail迭代器,即两者没有交叉,则互换元素,然后继续进行相同的动作,向中间逼近,直到两个迭代器交叉,结束一次分割。

看一张来自维基百科上关于快速排序的动态图片,帮助理解。
id="iframe_0.7141859656871277" src="data:text/html;charset=utf8,%3Cimg%20id=%22img%22%20src=%22http://7xqbsh.com1.z0.glb.clouddn.com/Sorting_quicksort_anim.gif?_=5256337%22%20style=%22border:none;max-width:848px%22%3E%3Cscript%3Ewindow.onload%20=%20function%20()%20%7Bvar%20img%20=%20document.getElementById('img');%20window.parent.postMessage(%7BiframeId:'iframe_0.7141859656871277',width:img.width,height:img.height%7D,%20'http://www.cnblogs.com');%7D%3C/script%3E" frameborder="0" scrolling="no" style="margin: 0px; padding: 0px; box-sizing: border-box; border-width: initial; border-style: none; width: 20px; height: 20px;">

内省式排序 Introsort

不当的枢轴选择,导致不当的分割,会使快速排序恶化为 O(n2)。David R.Musser于1996年提出一种混合式排序算法:Introspective Sorting(内省式排序),简称IntroSort,其行为大部分与上面所说的median-of-three Quick Sort完全相同,但是当分割行为有恶化为二次方的倾向时,能够自我侦测,转而改用堆排序,使效率维持在堆排序的 O(nlgn),又比一开始就使用堆排序来得好。


代码分析

下面是完整的SGI STL sort()源码(使用默认<操作符版)

1
2
3
4
5
6
7
8
9
10
11
12
template  < class  _RandomAccessIter>
inline  void  sort(_RandomAccessIter __first, _RandomAccessIter __last) {
   __STL_REQUIRES(_RandomAccessIter, _Mutable_RandomAccessIterator);
   __STL_REQUIRES( typename  iterator_traits<_RandomAccessIter>::value_type,
                  _LessThanComparable);
   if  (__first != __last) {
     __introsort_loop(__first, __last,
                      __VALUE_TYPE(__first),
                      __lg(__last - __first) * 2);
     __final_insertion_sort(__first, __last);
   }
}

其中,__introsort_loop便是上面介绍的内省式排序,其第三个参数中所调用的函数__lg()便是用来控制分割恶化情况,代码如下:

1
2
3
4
5
6
template  < class  Size>
inline  Size __lg(Size n) {
     Size k;
     for  (k = 0; n > 1; n >>= 1) ++k;
     return  k;
}

即求lg(n)(取下整),意味着快速排序的递归调用最多 2*lg(n) 层。

内省式排序算法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template  < class  _RandomAccessIter, class  _Tp, class  _Size>
void  __introsort_loop(_RandomAccessIter __first,
                       _RandomAccessIter __last, _Tp*,
                       _Size __depth_limit)
{
   while  (__last - __first > __stl_threshold) {
     if  (__depth_limit == 0) {
       partial_sort(__first, __last, __last);
       return ;
     }
     --__depth_limit;
     _RandomAccessIter __cut =
       __unguarded_partition(__first, __last,
                             _Tp(__median(*__first,
                                          *(__first + (__last - __first)/2),
                                          *(__last - 1))));
     __introsort_loop(__cut, __last, (_Tp*) 0, __depth_limit);
     __last = __cut;
   }
}
  1. 首先判断元素规模是否大于阀值__stl_threshold__stl_threshold是一个常整形的全局变量,值为16,表示若元素规模小于等于16,则结束内省式排序算法,返回sort函数,改用插入排序。
  2. 若元素规模大于__stl_threshold,则判断递归调用深度是否超过限制。若已经到达最大限制层次的递归调用,则改用堆排序。代码中的partial_sort即用堆排序实现。
  3. 若没有超过递归调用深度,则调用函数__unguarded_partition()对当前元素做一趟快速排序,并返回枢轴位置。__unguarded_partition()函数采用的便是上面所讲的使用两个迭代器的方法,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    template  < class  _RandomAccessIter, class  _Tp>
    _RandomAccessIter __unguarded_partition(_RandomAccessIter __first,
                                         _RandomAccessIter __last,
                                         _Tp __pivot)
    {
    while  ( true ) {
         while  (*__first < __pivot)
             ++__first;
         --__last;
         while  (__pivot < *__last)
             --__last;
         if  (!(__first < __last))
             return  __first;
         iter_swap(__first, __last);
         ++__first;
    }
    }
  4. 经过一趟快速排序后,再递归对右半部分调用内省式排序算法。然后回到while循环,对左半部分进行排序。源码写法和我们一般的写法不同,但原理是一样的,需要注意。

递归上述过程,直到元素规模小于__stl_threshold,然后返回sort函数,对整个元素序列调用一次插入排序,此时序列中的元素已基本有序,所以插入排序也很快。至此,整个sort函数运行结束。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值