STL sort 函数实现详解 作者:fengcc 原创作品 转载请注明出处 前几天阿里电话一面,被问到STL中sort函数的实现。以前没有仔细探究过,听人说是快速排序,于是回答说用快速排序实现的

来源:互联网 发布:雅居乐找软件测试 编辑:程序博客网 时间:2024/06/07 02:21

STL sort 函数实现详解

作者:fengcc 原创作品 转载请注明出处


前几天阿里电话一面,被问到STLsort函数的实现。以前没有仔细探究过,听人说是快速排序,于是回答说用快速排序实现的,但听电话另一端面试官的声音,感觉不对劲,知道自己回答错了。这几天特意看了一下,在此记录。


函数声明

#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迭代器,即两者没有交叉,则互换元素,然后继续进行相同的动作,向中间逼近,直到两个迭代器交叉,结束一次分割。

看一张来自维基百科上关于快速排序的动态图片,帮助理解。

内省式排序 Introsort

不当的枢轴选择,导致不当的分割,会使快速排序恶化为 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;  }}
  1. 首先判断元素规模是否大于阀值__stl_threshold__stl_threshold是一个常整形的全局变量,值为16,表示若元素规模小于等于16,则结束内省式排序算法,返回sort函数,改用插入排序。
  2. 若元素规模大于__stl_threshold,则判断递归调用深度是否超过限制。若已经到达最大限制层次的递归调用,则改用堆排序。代码中的partial_sort即用堆排序实现。
  3. 若没有超过递归调用深度,则调用函数__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;}}
  4. 经过一趟快速排序后,再递归对右半部分调用内省式排序算法。然后回到while循环,对左半部分进行排序。源码写法和我们一般的写法不同,但原理是一样的,需要注意。

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

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 大街网实习生招聘 大街校园招聘 大街网2013校园招聘 大街网2014校园招聘 2018女生很酷英文名不烂大街 大衣蝴蝶结 毛呢大衣 军大衣 毛大衣 羊剪绒大衣 大衣哥歌曲 军大衣图片 羊毛大衣和羊绒大衣区别 羊毛大衣羊绒大衣 派克大衣 大衣搭配 大衣图片 颗粒绒大衣 皮草大衣 大衣的款式 仿皮草大衣 男大衣 貂绒大衣 韩版大衣 大衣英语 男士大衣 男款大衣 黑色大衣 羊绒大衣女 羊毛大衣和羊绒大衣 女羊毛大衣 大衣定做 腰带大衣 大衣定制 加厚大衣女 大衣外套女 红大衣女 睡袍大衣 新款大衣 羊毛大衣水洗 大衣蝴蝶结打法