稍微花了一点点时间看了一下老师推荐的博客:http://feihu.me/blog/2014/sgi-std-sort/,看完后无不赞叹STL追求效率之极致,STL的sort排序算法综合了三种排序快排,堆排和插入排序,被称为Introspective Sort(内省式排序),在算法内部根据自身不一样的情形来判断来使用不一样的算法进行排序,sort算法能够说综合了三种排序算法的优势,追求效率到了极致。ios
一开始sort算法有两部分组成,__introsort_loop和__final_insertion_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); //std::sort的最后一步——插入排序。 __final_insertion_sort(first, last); } }
__introsort_loop便是Introspective Sort内省式排序,__final_insertion_sort是插入排序。首先讲内省式排序即sort算法的大头。算法
template <class RandomAccessIterator, class T, class Size> void __introsort_loop(RandomAccessIterator first, RandomAccessIterator last, T*,Size depth_limit) { while (last - first > __stl_threshold) { //depth_limit是前面所提到的判断分割行为是否有恶化倾向的阈值 if (depth_limit == 0) { //函数调用partial_sort,它即是堆排序 partial_sort(first, last, last); return; } --depth_limit; RandomAccessIterator cut = __unguarded_partition(first, last, T(__median(*first, *(first + (last - first) / 2), *(last - 1)))); __introsort_loop(cut, last, value_type(first), depth_limit); last = cut; } }
首先是while语句判断,last-first > __stl_threshold,__stl_threshold为最小分段阈值,值为16,大致上来讲,当数据量很大时,Introspective Sort都是采用快速排序,也就是__unguarded_partition,当last-first<16时,再使用递归来排序显然不划算,递归的开销相对来讲太大,这时候将退出函数执行__final_insertion_sort插入排序。另外,在递归部分,快速排序也作了优化,使用了单边循环的方式作递归,还有一点优化是pivot的取值,__median函数,它取首部、尾部和中部三个元素的中值做为pivot,而咱们平时直接用首部,中部或者尾部来作pivot,一般咱们并不比较他们的取值,在不少状况下这将引发递归的恶化,depth_limit就是用来判断分割行为是否有恶化倾向的阈值,当depth_limit减到0时将采用partial_sort堆排序。再说回单边循环的方式的递归,在用__unguarded_partition快排后取到index作cut,递归进入__introsort_loop,区间是[cut, last),下一行又将last赋值成cut即last = cut;单边递归完成后回到开始,又开始__unguarded_partition快排,此时的边区间就到了[first,cut),构成了一个左右的循环结构,而不是平时写的快排左右的递归结构,STL这样写节省了一半的函数递归调用,大大减小了开销(这样可读性就降低了,但为了效率,STL程序员也是拼到了极致)。该函数__introsort_loop退出条件是last-first < __stl_threshold即区域小于等于阈值16,或者超过递归深度阈值depth_limit,函数退出后进入到__final_insertion_sort插入排序,(这个时候序列的每一个子序列都有至关程度的排序,但又还没有彻底排序,使用插入排序再好不过)__final_insertion_sort也不是简单的插入排序,STL程序员们在简单的插入排序算法中进一步作了优化,将插入排序又分红了__unguarded_linear_insert,__linear_insert —— 无边界做检查的插入排序和边界做检查的插入排序。安全
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; } 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); }
__unguarded_linear_insert不对边界做检查。正由于如此,它必定比下面的__insertion_sort要快。那么为何插入排序又能够分红无边界做检查和边界做检查呢。回到sort函数中的__final_insertion_sort函数进行分析。dom
template <class RandomAccessIterator> void __final_insertion_sort(RandomAccessIterator first, RandomAccessIterator last) { //const int __stl_threshold = 16; 最小分段阈值 //一个带边界检查而另外一个不带,不带边界检查的__unguarded_insertion_sort更快。 if (last - first > __stl_threshold) { __insertion_sort(first, first + __stl_threshold); __unguarded_insertion_sort(first + __stl_threshold, last); } else //__insertion_sort和__unguarded_insertion_sort有何区别? //有无边界检测 __insertion_sort(first, last); }
咱们发现若是区间大小last-first>16时将采用__insertion_sort和__unguarded_insertion_sort,__insertion_sort有边界检测插入排序区间是[first,first+16],__unguarded_insertion_sort无边界检测插入排序区间是[first+17,last],last-first<16时直接采用有边界检测插入排序。重点在于为何在区间开始的__stl_threshold个元素以后就能用无边界检测插入排序呢,函数
进一步分析,oop
咱们首先只考虑最左边的子序列,先假设是因为第一种状况终止了这个函数,那么该子区域小于16。
由于使用的是快速排序,左边区间的全部数据必定比右边小,能够推断出最小值必定在该小于16的子区域内。
假设函数是第二种状况下终止,那么对于最左边的区间,因为递归深度过深,所以该区间会调用堆排序,因此这段区间的最小值必定位于最左端。
加上前面的分析:左边区间全部的数据必定比右边小,那么该区间内最左边的数据里必定有整个序列的最小值。
所以,不管是哪一种状况,均可以保证起始的16个元素中必定有最小值。
如此便可以使用__insertion_sort对前16个元素进行排序,接着用__unguarded_insertion_sort毫无顾忌的在不考虑边界的状况下对剩于的区间进行更快速的排序。学习
写到这里,sort算法差很少分析完了,最后说一下感想,写代码要看做是雕琢一件艺术品,只有通过不断的打磨,才能在稳定性、安全性、通用性和效率上经历住考验,永远不要以为本身写的代码已经到了完美了,要不断进行修改和优化,并且这个过程是不断进行的,只有不断追求卓越,才能到达顶峰。STL库积蓄了C++程序员们数十年的心血,里面的种种细节都值得咱们去学习,学海无涯,书山有路,最后祝你们早日达成本身的理想。优化
#include <iostream> const int __stl_threshold = 16; 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; } } template <class RandomAccessIterator, class T, class Compare> void __partial_sort(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, T*, Compare comp) { make_heap(first, middle, comp); for (RandomAccessIterator i = middle; i < last; ++i) if (comp(*i, *first)) __pop_heap(first, middle, i, T(*i), comp, distance_type(first)); sort_heap(first, middle, comp); } //partial_sort,堆排序 template <class RandomAccessIterator, class Compare> inline void partial_sort(RandomAccessIterator first, RandomAccessIterator middle, RandomAccessIterator last, Compare comp) { __partial_sort(first, middle, last, value_type(first), comp); } //Introspective Sorting(内省式排序) /* 该函数只有两种状况下可能返回, 一是区域小于等于阈值16;二是超过递归深度阈值。 咱们如今只考虑最左边的子序列,先假设是因为第一种状况终止了这个函数,那么该子区域小于16。 再根据前面的结论:左边区间的全部数据必定比右边小,能够推断出最小值必定在该小于16的子区域内。 假设函数是第二种状况下终止,那么对于最左边的区间,因为递归深度过深,所以该区间会调用堆排序,因此这段区间的最小值必定位于最左端。 再加上前面的结论:左边区间全部的数据必定比右边小,那么该区间内最左边的数据必定是整个序列的最小值。 所以,不管是哪一种状况,均可以保证起始的16个元素中必定有最小值。 如此便可以使用__insertion_sort对前16个元素进行排序,接着用__unguarded_insertion_sort毫无顾忌的在不考虑边界的状况下对剩于的区间进行更快速的排序。 */ template <class RandomAccessIterator, class T, class Size> void __introsort_loop(RandomAccessIterator first, RandomAccessIterator last, T*,Size depth_limit) { //const int __stl_threshold = 16; 最小分段阈值 //当数据长度小于该阈值时,再使用递归来排序显然不划算,递归的开销相对来讲太大。 //而此时整个区间内部有多个元素个数少于16的子序列, //每一个子序列都有至关程度的排序,但又还没有彻底排序,过多的递归调用是不可取的。 //而这种状况恰好插入排序最拿手,它的效率可以达到O(N)。 //所以这里停止快速排序,sort会接着调用外部的__final_insertion_sort x行 while (last - first > __stl_threshold) { //depth_limit是前面所提到的判断分割行为是否有恶化倾向的阈值 if (depth_limit == 0) { //函数调用partial_sort,它即是堆排序 partial_sort(first, last, last); return; } --depth_limit; //__unguarded_partition,这其实就是咱们日常所使用的快速排序主体部分,用于根据pivot将区间分割为两个子序列。 //__median函数,它的做用是取首部、尾部和中部三个元素的中值做为pivot。 //咱们以前学到的快速排序都是选择首部、尾部或者中间位置的元素做为pivot,并不会比较它们的值,在不少状况下这将引发递归的恶化。如今这里采用的中值法能够在绝大部分情形下优于原来的选择。 RandomAccessIterator cut = __unguarded_partition(first, last, T(__median(*first, *(first + (last - first) / 2), *(last - 1)))); //递归结构 /* 能够看出它是一个递归函数,由于咱们说过,Introspective Sort在数据量很大的时候采用的是正常的快速排序, 所以除了处理恶化状况之外,它的结构应该和快速排序一致。 但仔细看代码,先无论循环条件和if语句(它们即是处理恶化状况所用),循环的后半部分是用来递归调用快速排序。 */ //__introsort_loop中只有对右边子序列进行递归调用是否是? //左边的递归不见了。的确,这里的写法可读性相对来讲比较差,可是仔细一分析发现是有它的道理的,它并非没有管左子序列。 //注意看,在分割原始区域以后,对右子序列进行了递归,接下来的last = cut将终点位置调整到了分割点,那么此时的[first, last)区间就是左子序列了。 //又由于这是一个循环结构,那么在下一次的循环中,左子序列便获得了处理。只是并未以递归来调用! //二者区别就在于STL节省了接近一半的函数调用,因为每次的函数调用有必定的开销,所以对于数据量很是庞大时,这一半的函数调用可能可以省下至关可观的时间。 __introsort_loop(cut, last, value_type(first), depth_limit); last = cut; } } //__unguarded_linear_insert不对边界做检查。正由于如此,它必定比下面的__insertion_sort要快。 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; } 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> 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)); } //能够忽略掉这层aux函数的包装,它只是为了得到迭代器所指向的类型,其实这两个函数能够合并为一个。 template <class RandomAccessIterator, class T> void __unguarded_insertion_sort_aux(RandomAccessIterator first, RandomAccessIterator last, T*) { for (RandomAccessIterator i = first; i != last; ++i) __unguarded_linear_insert(i, T(*i)); } template <class RandomAccessIterator> inline void __unguarded_insertion_sort(RandomAccessIterator first, RandomAccessIterator last) { __unguarded_insertion_sort_aux(first, last, value_type(first)); } template <class RandomAccessIterator> void __final_insertion_sort(RandomAccessIterator first, RandomAccessIterator last) { //const int __stl_threshold = 16; 最小分段阈值 //一个带边界检查而另外一个不带,不带边界检查的__unguarded_insertion_sort更快。 if (last - first > __stl_threshold) { __insertion_sort(first, first + __stl_threshold); __unguarded_insertion_sort(first + __stl_threshold, last); } else //__insertion_sort和__unguarded_insertion_sort有何区别? //有无边界检测 __insertion_sort(first, last); } template <class RandomAccessIterator> inline void sort(RandomAccessIterator first, RandomAccessIterator last) { if (first != last) { __introsort_loop(first, last, value_type(first), __lg(last - first) * 2); //std::sort的最后一步——插入排序。 __final_insertion_sort(first, last); } }