做者:fengcc 原创做品 转载请注明出处面试
前几天阿里电话一面,被问到STL
中sort
函数的实现。之前没有仔细探究过,听人说是快速排序,因而回答说用快速排序实现的,但听电话另外一端面试官的声音,感受不对劲,知道本身回答错了。这几天特地看了一下,在此记录。算法
#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
提供了两种调用方式,一种是使用默认的<
操做符比较,一种能够自定义比较函数。但是为何它一般比咱们本身写的排序要快那么多呢?dom
原来,STL
中的sort
并不是只是普通的快速排序,除了对普通的快速排序进行优化,它还结合了插入排序和堆排序。根据不一样的数量级别以及不一样状况,能自动选用合适的排序方法。当数据量较大时采用快速排序,分段递归。一旦分段后的数据量小于某个阀值,为避免递归调用带来过大的额外负荷,便会改用插入排序。而若是递归层次过深,有出现最坏状况的倾向,还会改用堆排序。函数
普通快速排序算法能够叙述以下,假设S表明须要被排序的数据序列:oop
S
中的元素只有0个或1个,结束。S
中的任何一个元素做为枢轴pivot
。S
分割为L
、R
两端,使L
内的元素都小于等于pivot
,R
内的元素都大于等于pivot
。L
、R
递归执行上述过程。快速排序最关键的地方在于枢轴的选择,最坏的状况发生在分割时产生了一个空的区间,这样就彻底没有达到分割的效果。STL
采用的作法称为median-of-three
,即取整个序列的首、尾、中央三个地方的元素,以其中值做为枢轴。优化
分割的方法一般采用两个迭代器head
和tail
,head
从头端往尾端移动,tail
从尾端往头端移动,当head
遇到大于等于pivot
的元素就停下来,tail
遇到小于等于pivot
的元素也停下来,若head
迭代器仍然小于tail
迭代器,即二者没有交叉,则互换元素,而后继续进行相同的动做,向中间逼近,直到两个迭代器交叉,结束一次分割。ui
看一张来自维基百科上关于快速排序的动态图片,帮助理解。
code
不当的枢轴选择,致使不当的分割,会使快速排序恶化为 O(n2)。David R.Musser于1996年提出一种混合式排序算法:Introspective Sorting
(内省式排序),简称IntroSort
,其行为大部分与上面所说的median-of-three Quick Sort
彻底相同,可是当分割行为有恶化为二次方的倾向时,可以自我侦测,转而改用堆排序,使效率维持在堆排序的 O(nlgn),又比一开始就使用堆排序来得好。排序
下面是完整的SGI STL sort()
源码(使用默认<
操做符版)递归
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()
即是用来控制分割恶化状况,代码以下:
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) 层。
内省式排序算法以下:
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; } }
__stl_threshold
,__stl_threshold
是一个常整形的全局变量,值为16,表示若元素规模小于等于16,则结束内省式排序算法,返回sort
函数,改用插入排序。__stl_threshold
,则判断递归调用深度是否超过限制。若已经到达最大限制层次的递归调用,则改用堆排序。代码中的partial_sort
即用堆排序实现。__unguarded_partition()
对当前元素作一趟快速排序,并返回枢轴位置。__unguarded_partition()
函数采用的即是上面所讲的使用两个迭代器的方法,代码以下: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; } }
递归上述过程,直到元素规模小于__stl_threshold
,而后返回sort
函数,对整个元素序列调用一次插入排序,此时序列中的元素已基本有序,因此插入排序也很快。至此,整个sort
函数运行结束。
好了,今天就到这里了,相信你们对STL sort
也有了必定的了解,若是发现任何错误,欢迎你们批评指正,一块儿交流!