前面两篇文章介绍了快速排序的基础知识和优化方向,今天来看一下STL中的sort算法的底层实现和代码技巧。面试
众所周知STL是借助于模板化来支撑数据结构和算法的通用化,通用化对于C++使用者来讲已经很惊喜了,可是若是你看看STL开发者强大的阵容就意识到STL给咱们带来的惊喜毫不会止步于通用化,强悍的性能和效率是STL的更让人惊艳的地方。算法
STL极致表现的背后是大牛们炉火纯青的编程技艺和追求极致的工匠精神的切实体现。笔者能力所限,只能踏着前人的肩膀来和你们一块儿看看STL中sort算法的背后究竟隐藏着什么,是否是有种《走进科学》的既视感,让咱们开始今天的sort算法旅程吧!编程
在了解sort算法的实现以前先来看一个概念:内省式排序
,说实话笔者的语文水平确实通常,对于这个词语用在排序算法上总以为不通透,那就研究一下吧!后端
内省式排序英文是Introspective Sort,其中单词introspective是内省型的意思,仍是不太明白,继续搜索,看一下百度百科对这个词条的解释:缓存
内省(Introspection )在心理学中,它是心理学基本研究方法之一。内省法又称自我观察法。它是发生在内部的,咱们本身可以意识到的主观现象。也能够说是对于本身的主观经验及其变化的观察。正由于它的主观性,内省法自古以来就成为心理学界长期的争论。另外内省也可看做自我检讨,也是儒家强调的自我思考。从这个角度说能够应用于计算机领域,如Java内省机制和cocoa内省机制。
From 百度百科-内省-科普中国审核经过词条
好家伙,原来内省是个心理学名词,到这里笔者有些感受了,内省就是自省、自我思考、根据本身的主观经验来观察变化作出调整,而不是把但愿寄托于外界,而是本身的经验和能力。bash
通俗点说,内省算法不挑数据集,尽可能针对每种数据集都能给定对应的处理方法,让排序都能有时间保证。写到这里,让笔者脑海浮现了《倚天屠龙记》里面张无忌光明顶大战六大门派的场景,不管敌人多么强悍或者羸弱,我都按照本身的路子应对。网络
他强由他强,清风拂山岗;
他横由他横,明月照大江;
他自狠来他自恶,我自一口真气足。
---《九阳真经》达摩
哲学啊,确实这样的,咱们切换到排序的角度来看看内省是怎么样的过程。笔者理解的内省式排序算法就是不依赖于外界数据的好坏多寡,而是根据本身针对每种极端场景下作出相应的判断和决策调整,从而来适应多种数据集合展示出色的性能。数据结构
俗话说侠者讲究刀、枪、剑、戟、斧、钺、钩、叉等诸多兵器,这也告诉咱们一个道理没有哪一种兵器是无敌的,只有在某些场景下的明显优点,这跟软件工程没有银弹是同样的。dom
回到咱们的排序算法上,排序算法也可谓是百花齐放:冒泡排序、选择排序、插入排序、快速排序、堆排序、桶排序等等。数据结构和算法
虽然一批老一辈的排序算法是O(n^2)的,优秀的算法能够到达O(nlogn),可是即便都是nlogn的快速排序和堆排序都有各自的长短之处,插入排序在数据几乎有序的场景下性能能够到达O(n),有时候咱们应该作的不是冲突对比而是融合创新。
内省排序是由David Musser在1997年设计的排序算法。这个排序算法首先从快速排序开始,当递归深度超过必定深度(深度为排序元素数量的对数值)后转为堆排序,David Musser大牛是STL领域响当当的人物。
抛开语境一味地对比孰好孰坏其实都没有意义,内省式排序就是集大成者,为了能让排序算法达到一个综合的优异性能,内省式排序算法结合了快速排序、堆排序、插入排序,并根据当前数据集的特色来选择使用哪一种排序算法,让每种算法都展现本身的长处,这种思想确实挺启发人的。
前面提到了内省式排序主要结合了快速排序、堆排序、插入排序,那么不由要问,这三种排序是怎么排兵布阵的呢?知己知彼百战不殆,因此先看下三种排序的优缺点
吧!
优缺点也大体清楚了,因此能够猜测一下内省式排序在实际中是如何调度
使这三种排序算法的:
写到这里,笔者又天马行空地想到了一个场景:
2005年春晚小品中黄宏和巩汉林出演的《装修》中黄宏做为装修工人手拿一大一小两把锤子,大锤80小锤40,大小锤头切换使用。
其实跟内省排序切换排序算法是一个道理,因此技术源于生活又高于生活
,贴图一张你们一块儿体会一下:
用了不少篇幅来说内省思想和内省式排序,相信你们也已经get到了,因此咱们具体看下实现细节,这个才是本文的重点,咱们继续往下一块儿分析吧!
本文介绍的sort算法是基于SGI STL版本的,而且主要是以侯捷老师的《STL源码剖析》一书为蓝原本进行展开的,所以使用了不带仿函数的版本,让咱们来一块儿领略大牛们的杰做吧!图为笔者买了好久却一直压箱底的STL神书:
SGI STL中的sort的参数是两个随机存取迭代器RandomAccessIterator,sort的模板也是基于此种迭代器的,所以若是容器不是随机存取迭代器,那么可能没法使用通用的sort函数。
综上咱们能够知道,sort算法能够很好的适用于vector和deque这两种容器。
前面介绍了内省式排序,因此看下sort是怎么一步步来使用introsort的,上一段入口代码:
template <class RandomAccessIterator> inline void sort(RandomAccessIterator first, RandomAccessIterator last) {
if (first != last) {
__introsort_loop(first, last, value_type(first), __lg(last - first) * 2);
__final_insertion_sort(first, last);
}
}复制代码
从代码来看sort使用了first和last两个随机存取迭代器,做为待排序序列的开始和终止,进一步调用了__introsort_loop和__final_insertion_sort两个函数,从字面上看前者是内省排序循环,后者是插入排序。其中注意到__introsort_loop的第三个参数__lg(last - first)*2,凭借咱们的经验来猜(蒙)一下吧,应该递归深度的限制,不急看下代码实现:
template <class Size>
inline Size __lg(Size n){
Size k;
for(k = 0;n > 1;n >>= 1) ++k;
return k;
}复制代码
这段代码的意思就是n=last-first,2^k<=n的最大整数k值。
因此总体看当假设last-first=20时,k=4,最大分割深度depth_max=4*2=8,从而咱们就能够根据first和last来肯定递归的最大深度了。
__introsort_loop函数中主要封装了快速排序和堆排序,来看看这个函数的实现细节:
//sort函数的入口
template <class RandomAccessIterator, class T, class Size>
void __introsort_loop(RandomAccessIterator first,
RandomAccessIterator last, T*,
Size depth_limit) {
while (last - first > __stl_threshold) {
if (depth_limit == 0) {
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;//迭代器交换 切换到左序列
}
}
//基于三点中值法的分区算法
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;
}复制代码
各位先不要晕更不要蒙圈,一点点分析确定能够拿下的。
前面提到了在sort中快速排序的写法和咱们以前见到的有一些区别,看了一下《STL源码剖析》对快排左序列的处理,侯捷老师是这么写的:"写法可读性较差,效率并无比较好",看到这里更蒙圈了,不过也试着分析一下吧!
图为:STL源码剖析中侯捷老师对该种写法的注释
常见写法:
//快速排序的常见写法伪代码
quicksort(arr,left,right){
pivoit = func(arr);//使用某种方法获取基准值
cut = partition(left,right,pivot);//左右边界和基准值来共同肯定分割点位置
quicksort(arr,left,cut-1);//递归处理左序列
quicksort(arr,cut+1,right);//递归处理右序列
}复制代码
SGI STL中的写法:
stl_quicksort(first,last){
//循环做为外层控制结构
while(ok){
cut = stl_partition(first,last,_median(first,last));//分割分区
stl_quicksort(cut,last);//递归调用 处理右子序列
last = cut;//cut赋值为last 至关于切换到左子序列 再继续循环
}
}复制代码
网上有一些大佬的文章说sgi stl中快排的写法左序列的调用借助了while循环节省了一半的递归调用,是典型的尾递归优化思路.
这里我暂时尚未写测试代码作对比,先占坑后续写个对比试验,再来评论吧,不过这种sgi的这种写法能够看看哈。
//注:这个是带自定义比较函数的堆排序版本
//堆化和堆顶操做
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);
}
//堆排序的入口
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);
}复制代码
__introsort_loop达到__stl_threshold阈值以后,能够认为数据集近乎有序了,此时就能够经过插入排序来进一步提升排序速度了,这样也避免了递归带来的系统消耗,看下__final_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);
}复制代码
来分析一下__final_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);
}
//__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));
}复制代码
在插入函数中一样出现了__unguarded_xxx这种形式的函数,unguarded单词的意思是无防备的,无保护的,侯捷大大提到这种函数形式是特定条件下免去边界检验条件也能正确运行的函数。
copy_backward也是一种总体移动的优化,避免了one by one的调整移动,底层调用memmove来高效实现。
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));
}复制代码
关于插入排序的这两个函数的实现和目的用途,展开起来会很细致,因此后面想着单独在写插入排序时单独拿出了详细学习一下,因此本文就暂时先不深究了,感兴趣的读者能够先行阅读相关资料,后续咱们再共同辩驳哈!
本文主要阐述了内省式排序的思想和基本实现思路,而且以此为切入点对sgi stl中sort算法的实现来进行了一些解读。
stl的做者们为了追求极致性能因此使用了大量的技巧,对此本文并无过多展开,也主要是段位不过高怕解读错了,嘿嘿…,不事后续能够尝试一点点剖析来一探大牛们的巅峰技艺。
00.快速排序的优化
01.你真的懂快速排序吗
02.白话分布式系统中的一致性哈希算法
03.深刻理解跳表在Redis中的应用
04.理解堆和堆排序
05.白话布隆过滤器BloomFilter
06.深刻理解IO复用之epoll
07.理解缓存系统的三个问题
08.聊聊后端面试中的一些问题和思考