当前位置: 首页 > 公众号精选 > C语言与CPP编程
[导读]1.写在前面 周六了...依然跳票...没有新文章产出...因为很忙...是的... 为了证明笔者没有放弃这块阵地,整合三篇去年的文章,今天一起来学习一下:快速排序及其优化 和 STL的sort算法 通过本文你将了解到以下内容: 快速排序的基本思想 快速排序的递归实现和

1.写在前面

周六了...依然跳票...没有新文章产出...因为很忙...是的...

为了证明笔者没有放弃这块阵地,整合三篇去年的文章,今天一起来学习一下: 快速排序及其优化 和  STL的sort算法

通过本文你将了解到以下内容:

  • 快速排序的基本思想
  • 快速排序的递归实现和迭代实现
  • 快速排序的最坏情况
  • 快速排序和归并排序对比
  • 快速排序的多角度优化
  • 内省式排序基本原理
  • STL的sort算法基本原理

2. 那年初识快排

2.1 看似青铜实则王者

常见不等同于简单。

很多人提起快排和二分都觉得很容易的样子,但是让现场Code很多就翻车了,就算可以写出个递归版本的代码,但是对其中的复杂度分析、边界条件的考虑、非递归改造、代码优化等就无从下手,填鸭背诵基本上分分钟就被面试官摆平了。

快速排序Quicksort又称划分交换排序partition-exchange sort,简称快排,一种排序算法。最早由C. A. R. Hoare教授在1960年左右提出,在平均状况下,排序n个项目要O(nlogn)次比较。

在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他算法更快,因为它的内部循环可以在大部分的架构上很有效率地达成。

快排的提出者是大名鼎鼎的人物,go语言使用的并发模型CSP也是这个大神提出的,一起膜拜下。

查尔斯·安东尼·理查德·霍尔爵士(Sir Charles Antony Richard Hoare缩写为C. A. R. Hoare,1934年1月11日-),昵称为东尼·霍尔(Tony Hoare),生于大英帝国锡兰可伦坡(今斯里兰卡),英国计算机科学家,图灵奖得主。

他设计了快速排序算法、霍尔逻辑、交谈循序程式。在操作系统中,他提出哲学家就餐问题,并发明用来作为同步程序的监视器(Monitors)以解决这个问题。他同时证明了监视器与信号标(Semaphore)在逻辑上是等价的。

1980年获颁图灵奖、1982年成为英国皇家学会院士、2000年因为他在计算机科学与教育方面的杰出贡献,获得英国王室颁赠爵士头衔、2011年获颁约翰·冯诺依曼奖,现为牛津大学荣誉教授,并在剑桥微软研究院担任研究员。

2.2 快速排序的基本思想和过程

2.2.1 D&C分治思想

在计算机科学中,分治法(Divide&Conquer)是建基于多项分支递归的一种很重要的算法范式,快速排序是分治思想在排序问题上的典型应用。

所谓分治思想D&C就是把一个较大规模的问题拆分为若干小规模且相似的问题。再对小规模问题进行求解,最终合并所有小问题的解,从而形成原来大规模问题的解。

字面上的解释是"分而治之",这个技巧是很多高效算法的基础,如排序算法(归并排序、快速排序)、傅立叶变换(快速傅立叶变换)。

分治法中最重要的部分是循环递归的过程,每一层递归有三个具体步骤:

  • 分解:将原问题分解为若干个规模较小,相对独立,与原问题形式相同的子问题。
  • 解决:若子问题规模较小且易于解决时,则直接解。否则,递归地解决各子问题。
  • 合并:将各子问题的解合并为原问题的解。

2.2.2 基本过程

快速排序使用分治法来把一个序列分为小于基准值和大于基准值的两个子序列。递归地排序两个子序列,直至最小的子序列长度为0或者1,整个递归过程结束,详细步骤为:

  • 挑选基准值: 从数列中挑出一个元素称为基准pivot,选取基准值有数种具体方法,此选取方法对排序的时间性能有决定性影响。

  • 基准值分割: 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面,与基准值相等的数可以到任何一边,在这个分割结束之后,对基准值的排序就已经完成。

  • 递归子序列: 递归地将小于基准值元素的子序列和大于基准值元素的子序列排序,步骤同上两步骤,递归终止条件是序列大小是0或1,因为此时该数列显然已经有序。

3. 快速的递归实现和迭代实现

快速排序一般大家写递归的时候更多,但是递归往往也会写出不同风格的版本,所以我们一起来看下多个风格的递归版本和迭代版本的实现,多种代码对比会让我们理解更深刻。

3.1 递归实现代码

C语言递归版本一:


C++递归版本二:

两个版本均可正确运行,但代码有一点差异:

  • 版本一 使用双指针交替从左(右)两边分别开始寻找大于基准值(小于基准值),然后与基准值交换,直到最后左右指针相遇。

  • 版本二 使用双指针向中间集合,左指针遇到大于基准值时则停止,等待右指针,右指针遇到小于基准值时则停止,与左指针指向的元素交换,最后基准值放到合适位置。

3.2 递归实现过程演示

过程说起来比较抽象,稳住别慌!灵魂画手画图来演示这两个过程。

3.2.1 C版本一过程演示

第一次递归循环为例:


步骤1: 选择第一个元素为基准值pivot=a[left]=5,right指针指向尾部元素,此时先由right自右向左扫描直至遇到<5的元素,恰好right起步元素4<5,因此需要将4与5互换位置;

步骤2: 4与5互换位置之后,轮到left指针从左向右扫描,注意一下left的起步指针指向了由步骤1交换而来的4,新元素4不满足停止条件,因此left由绿色虚箭头4位置游走到元素9的位置,此时left找到9>5,因此将此时left和right指向的元素互换,也就是元素5和元素9互换位置;

步骤3: 互换之后right指针继续向左扫描,从蓝色虚箭头9位置游走到3的位置,此时right发现3<5,因此将此时left和right指向的元素互换,也就是元素3和元素5互换位置;

步骤4: 互换之后left指针继续向右扫描,从绿色虚箭头3位置游走到6的位置,此时left发现6>5,因此将此时left和right指向的元素互换,也就是元素6和元素5互换位置;

步骤5: 互换之后right指针继续向左扫描,从蓝色虚箭头6位置一直游走到与left指针相遇,此时二者均停留在了pivot=5的新位置上,且左右两边分成了两个相对于pivot值的子序列;


循环结束:至此出现了以5为基准值的左右子序列,接下来就是对两个子序列实施同样的递归步骤。


第二次和第三次左子序列递归循环为例:



步骤1-1:选择第一个元素为基准值pivot=a[left]=4,right指针指向尾部元素,此时先由right指针向左扫描,恰好起步元素3<4,因此将3和4互换;

步骤1-2:互换之后left指针从元素3开始向右扫描,一直游走到与right指针相遇,此时本次循环停止,特别注意这种情况下可以看到基准值4只有左子序列,无右子序列,这种情况是一种退化,就像冒泡排序每次循环都将基准值放置到最后,因此效率将退化为冒泡的O(n^2);


步骤1-3:选择第一个元素为基准值pivot=a[left]=3,right指针指向尾部元素,此时先由right指针向左扫描,恰好起步元素1<3,因此将1和3互换;

步骤1-4:互换之后left指针从1开始向右扫描直到与right指针相遇,此时注意到pivot=3无右子序列且左子序列len=1,达到了递归循环的终止条件,此时可以认为由第一次循环产生的左子序列已经全部有序。


循环结束:至此左子序列已经排序完成,接下来对右子序列实施同样的递归步骤,就不再演示了,聪明的你一定get到了。


特别注意:

以上过程中left和right指针在某个元素相遇,这种情况在代码中是不会出现的,因为外层限制了i!=j,图中之所以放到一起是为了直观表达终止条件。

3.2.2 C++版本二过程演示

分析一下:

个人觉得这个版本虽然同样使用D&C思想但是更加简洁,从动画可以看到选择pivot=a[end],然后左右指针分别从index=0和index=end-1向中间靠拢。

过程中扫描目标值并左右交换,再继续向中间靠拢,直到相遇,此时再根据a[left]和a[right]以及pivot的值来进行合理置换,最终实现基于pivot的左右子序列形式。

脑补场景:

上述过程让我觉得很像统帅命令左右两路军队从两翼会和,并且在会和过程中消灭敌人有生力量(认为是交换元素),直到两路大军会师。

此时再将统帅王座摆到正确的位置,此过程中没有统帅王座的反复变换,只有最终会师的位置,以王座中心形成了左翼子序列和右翼子序列,再重复相同的过程,直至完成大一统。

脑补不过瘾 于是凑图一张:

3.3 多种递归版本说明

虽然快排的递归版本是基于D&C实现的,但是由于pivot值的选择不同、交换方式不同等诸多因素,造成了多种版本的递归代码。

并且内层while循环里面判断>=还是>(即是否等于的问题),外层循环判断本序列循环终止条件等写法都会不同,因此在写快排时切忌死记硬背,要不然边界条件判断不清楚很容易就死循环了。

看下上述我贴的两个版本的代码核心部分:

另外在网上很多大神的博客里面还进行了多种模式的快排:单轴模式、双向切分、三项切分、多基准值等新花样,感兴趣可以参考快速排序算法的多种实现。

其实无论哪种写法都需要明确知道自己是交换、还是覆盖、基准值选取位置、>=和<=的等号问题、循环终止条件等,这样才能写出BugFree的快速排序算法。

网上很多代码的核心部分是这样写的:

覆盖or交换

代码中首先将pivot的值引入局部变量保存下来,这样就认为A[L]这个位置是个坑,可以被其他元素覆盖,最终再将pivot的值填到最后的坑里。

这种做法也没有问题,因为你只要画图就可以看到,每次坑的位置是有相同元素的位置,也就是被备份了的元素。

个人感觉 与其叫坑不如叫备份,但是如果你代码使用的是基于指针或者引用的swap,那么就没有坑的概念了。

这就是覆盖和交换的区别,本文的例子都是swap实现的,因此没有坑位被最后覆盖一次的过程。

3.4 迭代版本实现

所谓迭代实现就是非递归实现一般使用循环来实现,我们都知道递归的实现主要是借助系统内的栈来实现的。

如果调用层级过深需要保存的临时结果和关系会非常多,进而造成StackOverflow栈溢出。

Stack一般是系统分配空间有限内存连续速度很快,每个系统架构默认的栈大小不一样,笔者在x86-CentOS7.x版本使用ulimit -s查看是8192Byte。

避免栈溢出的一种办法是使用循环,以下为笔者验证的使用STL的stack来实现的循环版本,代码如下:

4. 快速排序的优化

快速排序是图领奖得主发明的算法,被誉为20世纪最重要的十大算法之一,快速排序为了可以在多种数据集都有出色的表现,进行了非常多的优化,因此对我们来说要深入理解一种算法的最有效的手段就是不断优化提高性能。

4.1 快速排序vs归并排序

快速排序和归并排序采用的基本思想都是分治思想Divide&Conquer,从D&C思想来看最主要的部分就是分割和合并,两种算法在使用D&C时侧重点有一些差异:

归并排序在分割时处理很简单,在合并时处理比较多,重点在合并。

快速排序在分割时处理比较复杂,由于交换的存在递归结束时就相当于合并完成了,重点在分割。

归并排序分治示意图:

快速排序分治示意图:

注:快排的过程就不写具体的数字了 仅为达意 点到即可。

可以明显看出来,快速排序在选择基准值时对整个分治过程影响很大,因为下一个环节的分治是基于前一环节的分割结果进行的。

4.2 分区不均匀和最坏复杂度

4.2.1 极端分区

考虑一种极端的情况下,如果基准值选取的不合理,比如是最大的或者最小的,那么将导致只有一边有数据,对于已经排序或者近乎有序的数据集合来说就可能出现这种极端情况,还是来画个图看下:

图中展示了每次分治都选择第一个元素作为基准值,但是每次的基准值都是最小值,导致每次基准值左侧没有子序列,除了基准值之外全部元素都在右子序列。

4.2.2 最坏情况概率和复杂度计算

每次分割排序之后,只能在有序序列中增加1个元素递归树变成了单支树并且递归深度变大,极端情况的出现概率和最坏复杂度计算如下:

极端情况概率就是每次在剩余所有元素中挑出最小的,这样每次的概率都是1/(n-i),所以组合起来就是1/(n!),所以随机数据集合出现最差情况的概率非常低,但是有序数据下固定基准值选择就可能造成极端情况的出现。

最坏复杂度相当于每次从n-i个元素中只找到1个数据,将所有情况累加也就达到了O(n^2)级别,并不是递归过程全都挑选了最值作为基准值才会出现O(n^2)的复杂度,复杂度是一个概率化的期望值,具体的系数不同影响也很大。

4.3 基准值选取优化

分割越均匀速度越快 

从上面的几张图可以清晰看到基准值的不同对于D&C过程的分割会产生很大的影响,为了保证快速排序的在通用数据集的效率,因此我们需要在基准值的选取上做一些决策,换句话说就是让选取的基准值每次都可以尽可能均匀地分割数据集,这样的效率是最高的。

随机选取基准值 

网上有很多选择方法比如固定选取第一个、固定选取最后一个、固定选择中间值、三值平均选取等,不过个人觉得每一次都随机选取某个位置的数据作为基准值,然后与第一个值互换,这样就相当于每次的基准值都是随机选择的,就将固定index带来的问题,大大降低了。

随机vs固定对比试验 

接下来做一组对比试验,生成一个0-100000的有序数组,代码增加了很多选择项和时间测量代码,测试代码如下:

笔者使用相同的数据集在fix和random模式下,后者的耗时明显低于前者,所以某些场景下随机化带来的性能提升很明显,是一个惯用的优化方法。

4.4 三分区模式优化

前面的路子都是以基准值为准分为小于子序列和大于子序列,考虑一种特殊的数据集,数据集中有大量重复元素,这种情况下使用两分区递归会对大量重复元素进行处理。

一个优化的方向就是使用三分区模式:小于区间、等于区间、大于区间,这样在后续的处理中则只需要处理小于区和大于区,降低了等于基准值区间元素的重复处理,加速排序过程。

4.4.1 三分区原理

如图为三分区模式中某个时刻的快照,其中展示了几个关键点和区间,包括基准值、小于区、等于区、处理值、待处理区、大于区

在实际过程中根据处理值与基准值的大小关系,进行相应分区合并和交换,再进行下标移动就可以了,实际中分三种情况,这也是写代码的依据:

  1. 处理值e==p,将e合并到等于区,i++;

  2. 处理值e<p,将e与(lt+1)位置的数据交换,扩展小于区lt++,等于区长度不变,相当于整体平移;

  3. 处理值e>p,将e与(gt-1)位置的数据交换,扩展大于区gt--,此时i不变,交换后的值是之前待处理区的尾部数据;

  • e==p的合并

  • e<p的合并

  • e>p的合并

  • 分区最终调整

处理完待处理区的全部数据之后的调整也非常重要,主要是将基准值P与lt位置的数据交换,从而实现最终的三分区,如图所示:

从最终的分区可以看到,我们下一次的循环可以不处理等于区的数据而只处理两端分区数据,这样在大量重复场景下优化效果会非常明显。

4.4.2 三分区实验

笔者使用相同的数据集在二分区模式下测试10w数据规模耗时大约是1800ms,数据集减少10倍耗时却增大了几十倍,或许二分区代码还是存在优化空间,不过这个对比可以看到存在大量重复元素时三分区性能还是很不错的。

4.5 快排优化小结

对快速排序的优化主要体现在基准值选取、数据集分割、递归子序列选取、其他排序算法混合等方面,换句话说就是让每次的分区尽量均匀且没有重复被处理的元素,这样才能保证每次递归都是高效简洁的。

5. STL的sort算法

在了解sort算法的实现之前先来看一个概念:内省式排序,说实话笔者的语文水平确实一般,对于这个词语用在排序算法上总觉得不通透,那就研究一下吧!

5.1 内省思想

内省式排序英文是Introspective Sort,其中单词introspective是内省型的意思,还是不太明白,继续搜索,看一下百度百科对这个词条的解释:

内省(Introspection )在心理学中,它是心理学基本研究方法之一。内省法又称自我观察法。它是发生在内部的,我们自己能够意识到的主观现象。也可以说是对于自己的主观经验及其变化的观察。

正因为它的主观性,内省法自古以来就成为心理学界长期的争论。另外内省也可看作自我反省,也是儒家强调的自我思考。从这个角度说可以应用于计算机领域,如Java内省机制和cocoa内省机制。

From 百度百科-内省-科普中国审核通过词条

原来内省是个心理学名词,到这里笔者有些感觉了,内省就是自省、自我思考、根据自己的主观经验来观察变化做出调整,而不是把希望寄托于外界,而是自己的经验和能力。

通俗点说,内省算法不挑数据集,尽量针对每种数据集都能给定对应的处理方法,让排序都能有时间保证。写到这里,让笔者脑海浮现了《倚天屠龙记》里面张无忌光明顶大战六大门派的场景,无论敌人多么强悍或者羸弱,我都按照自己的路子应对。

原来内省是个心理学名词,到这里笔者有些感觉了,内省就是自省、自我思考、根据自己的主观经验来观察变化做出调整,而不是把希望寄托于外界,而是自己的经验和能力。

通俗点说,内省算法不挑数据集,尽量针对每种数据集都能给定对应的处理方法,让排序都能有时间保证。写到这里,让笔者脑海浮现了《倚天屠龙记》里面张无忌光明顶大战六大门派的场景,无论敌人多么强悍或者羸弱,我都按照自己的路子应对。

5.2 内省排序概况

俗话说侠者讲究刀、枪、剑、戟、斧、钺、钩、叉等诸多兵器,这也告诉我们一个道理没有哪种兵器是无敌的,只有在某些场景下的明显优势,这跟软件工程没有银弹是一样的。

回到我们的排序算法上,排序算法也可谓是百花齐放:冒泡排序、选择排序、插入排序、快速排序、堆排序、桶排序等等。

虽然一批老一辈的排序算法是O(n^2)的,优秀的算法可以到达O(nlogn),但是即使都是nlogn的快速排序和堆排序都有各自的长短之处,插入排序在数据几乎有序的场景下性能可以到达O(n),有时候我们应该做的不是冲突对比而是融合创新

内省排序是由David Musser在1997年设计的排序算法。这个排序算法首先从快速排序开始,当递归深度超过一定深度(深度为排序元素数量的对数值)后转为堆排序,David Musser大牛是STL领域响当当的人物。

抛开语境一味地对比孰好孰坏其实都没有意义,内省式排序就是集大成者,为了能让排序算法达到一个综合的优异性能,内省式排序算法结合了快速排序、堆排序、插入排序,并根据当前数据集的特点来选择使用哪种排序算法,让每种算法都展示自己的长处,这种思想确实挺启发人的。

5.3 内省排序排兵布阵

前面提到了内省式排序主要结合了快速排序、堆排序、插入排序,那么不禁要问,这三种排序是怎么排兵布阵的呢?知己知彼百战不殆,所以先看下三种排序的优缺点吧!

  • 快速排序 在大量数据时无论是有序还是重复,使用优化后的算法大多可以到达O(nlogn),虽然堆排序也是O(nlogn)但是由于某些原因快速排序会更快一些,当递归过深分割严重不均匀情况出现时会退化为O(n^2)的复杂度,这时性能会打折扣,这也就是快速排序的短处了。


  • 堆排序 堆排序是快速排序的有力竞争者,最大的特点是可以到达O(nlogn)并且复杂度很稳定,并不会像快速排序一样可能退化为O(n^2),但是堆排序过程中涉及大量堆化调整,并且元素比较是跳着来的对Cache的局部性特征利用不好,以及一些其他的原因导致堆排序比快速排序更慢一点,但是大O复杂度仍然是一个级别的。


  • 插入排序 插入排序的一个特点是就像我们玩纸牌,在梳理手中的牌时,如果已经比较有序了,那么只需要做非常少的调整即可,因此插入排序在数据量不大且近乎有序的情况下复杂度可以降低到O(n),这一点值得被应用。

优缺点也大致清楚了,所以可以猜想一下内省式排序在实际中是如何调度使这三种排序算法的:

  • 启动阶段 面对大量的待排序元素,首先使用快速排序进行大刀阔斧排序,复杂度可以在O(nlogn)运行

  • 深入阶段 在快速排序使用递归过程中,涉及栈帧保存切换等诸多递归的操作,如果分区切割不当递归过深可能造成栈溢出程序终止,因此如果快速排序过程中退化为O(n^2),此时会自动检测切换为堆排序,因为堆排序没有恶化情况,都可以稳定在O(nlogn)

  • 收尾阶段 在经过快排和堆排的处理之后,数据分片的待排序元素数量小于某个经验设定值(可以认为是递归即将结束的前几步调用)时,数据其实已经几乎有序,此时就可以使用插入排序来提高效率,将复杂度进一步降低为O(n)


5.4 sort算法细节

本文介绍的sort算法是基于SGI STL版本的,并且主要是以侯捷老师的《STL源码剖析》一书为蓝本来进行展开的,因此使用了不带仿函数的版本。

SGI STL中的sort的参数是两个随机存取迭代器RandomAccessIterator,sort的模板也是基于此种迭代器的,因此如果容器不是随机存取迭代器,那么可能无法使用通用的sort函数。

  • 关联容器 map和set底层是基于RB-Tree,本身就已经自带顺序了,因此不需要使用sort算法
  • 序列容器 list是双向迭代器并不是随机存取迭代器,vector和deque是随机存取迭代器适用于sort算法
  • 容器适配器 stack、queue和priority-queue属于限制元素顺序的容器,因此不适用sort算法。

综上我们可以知道,sort算法可以很好的适用于vector和deque这两种容器。

前面介绍了内省式排序,所以看下sort是怎么一步步来使用introsort的,上一段入口代码:

从代码来看sort使用了first和last两个随机存取迭代器,作为待排序序列的开始和终止,进一步调用了__introsort_loop和__final_insertion_sort两个函数,从字面上看前者是内省排序循环,后者是插入排序。其中注意到__introsort_loop的第三个参数__lg(last - first)*2,凭借我们的经验来猜(蒙)一下吧,应该递归深度的限制,不急看下代码实现:

这段代码的意思就是n=last-first,2^k<=n的最大整数k值。

所以整体看当假设last-first=20时,k=4,最大分割深度depth_max=4*2=8,从而我们就可以根据first和last来确定递归的最大深度了。

快速排序和堆排序的配合

__introsort_loop函数中主要封装了快速排序和堆排序,来看看这个函数的实现细节:

各位先不要晕更不要蒙圈,一点点分析肯定可以拿下的

  • 先看参数两个随机存取迭代器first和last,第三个参数是__lg计算得到的分割深度;

  • 这时候我们进入了while判断了last-first的区间大小,__stl_threshold为16,侯捷大大特别指出__stl_threshold的大小可以是5~20,具体大小可以自己设置,如果大于__stl_threshold那就才会继续执行,否则跳出;

  • 假如现在区间大小大于__stl_threshold,判断第三个参数depth_limit是否为0,也就是是否出现了分割过深的情况,相当于给了一个初始最大值,然后每分割一次就减1,直到depth_limit=0,这时候调用partial_sort,从《stl源码剖析》的其他章节可以知道,partial_sort就是对堆排序的封装,看到这里有点意思了主角之一的heapsort出现了;

  • 继续往下看,depth_limit>0 尚有分割余额,那就燥起来吧!这样来到了__unguarded_partition,这个函数从字眼看是快速排序的partiton过程,返回了cut随机存取迭代器,__unguarded_partition的第三个参数__median使用的是三点中值法来获取的基准值Pivot,至此快速排序的partition的三个元素集齐了,最后返回新的切割点位置;

  • 继续看马上搞定啦,__introsort_loop出现了,果然递归了,特别注意一下这里只有一个递归,并且传入的是cut和last,相当于右子序列,那左子序列怎么办啊?别急往下看,last=cut峰回路转cut变成了左子序列的右边界,这样就开始了左子序列的处理;

快速排序的实现对比

前面提到了在sort中 快速排序的写法和我们之前见到的有一些区别,看了一下《STL源码剖析》对快排左序列的处理,侯捷老师是这么写的:"写法可读性较差,效率并没有比较好",看到这里更蒙圈了,不过也试着分析一下吧!

图为:STL源码剖析中侯捷老师对该种写法的注释

常见写法:

SGI STL中的写法:

网上有一些大佬的文章说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的实现细节吧!

  • 引入参数随机存取迭代器first和last

  • 如果last-first > __stl_threshold不成立就调用__insertion_sort,这个相当于元素数比较少了可以直接调用,不用做特殊处理;

  • 如果last-first > __stl_threshold成立就进一步再分割成两部分,分别调用__insertion_sort和__unguarded_insertion_sort,两部分的分割点是__stl_threshold,不免要问这俩函数有啥区别呀?

__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, lastlast + 1);//区间移动
        *first = value;
    }
    else
        __unguarded_linear_insert(last, value);
}

//__insertion_sort入口
template <class RandomAccessIterator>
void __insertion_sort(RandomAccessIterator first, RandomAccessIterator last) {
    if (first == lastreturn
    for (RandomAccessIterator i = first + 1; i != last; ++i)
        __linear_insert(first, i, value_type(first));
}

在插入函数中同样出现了__unguarded_xxx这种形式的函数,unguarded单词的意思是无防备的,无保护的,侯捷大大提到这种函数形式是特定条件下免去边界检验条件也能正确运行的函数。

copy_backward也是一种整体移动的优化,避免了one by one的调整移动,底层调用memmove来高效实现。

__unguarded_insertion_sort的实现
template <class RandomAccessIteratorclass T>
void __unguarded_insertion_sort_aux(RandomAccessIterator first
                                    RandomAccessIterator lastT*) {

    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));
}


关于插入排序的这两个函数的实现和目的用途,展开起来会很细致,所以后面想着单独在写插入排序时单独拿出了详细学习一下。

6.写在最后

忙碌的日子里自己的时间就变得很少,然后开始思考未来,其实这样并不好。

不多说了,感谢各位读者的倾情阅读,笔芯你们。

巨人的肩膀

  • http://feihu.me/blog/2014/sgi-std-sort/
  • https://liam.page/2018/09/18/std-sort-in-STL/
  • https://zhuanlan.zhihu.com/p/36274119
  • 侯捷《 STL源码剖析》第六章


免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读
[信息速递]

总裁离职!半导体巨头官宣重组

业内消息,近日半导体巨头意法半导体(ST)官宣将进行重组,该公司将从三个产品部门(ADG、MDG和AMS)过渡到两个产品部门(APMS和MDRF),且ST前汽车和分立产品集团总裁Marco·Monti也将离开公司。

关键字: 意法半导体 ST
[刘岩轩]

传感器和AI相结合,ST智能传感器助力未来可持续的虚实交融生活

时间来到2023年,ST在中国召开了其首届传感器大会,支持本地端的AI计算的智能传感器成为了本次大会的焦点。在开幕演讲上,意法半导体副总裁·中国区总经理曹志平表示,我们的生活经历了从off-line到on-line的变革...

关键字: 传感器 AI ST 可持续 MEMS ISPU
[嵌入式分享]

入门级mcu产品有哪些?都具有哪些特点?

MCU(微控制单元)俗称单片机,可被认为是CPU的缩减版本,把CPU的频率与规格进行缩减处理,并将RAM、ROM、时钟、A/D转换、定时/计数器、UART 、DMA等电路单元,甚至包括USB接口、LCD驱动电路都整合在一...

关键字: 入门级mcu ST
[产业新闻]

"台达零碳工厂"亮相2023工博会

推广五星零碳工厂成功经验 积极响应"双碳"目标  上海2023年9月21日 /美通社/ -- 全球电源管理、散热解决方案暨自动化厂商台达以"台达零碳工厂"为主...

关键字: BSP 可持续发展 微电网 ST
[产业新闻]

DSC2023数服会将举办 共赴数智之约 共谋高质量发展

北京2023年9月19日 /美通社/ -- 科技创新与数字化服务领域极具影响力的年度盛会——STIF2023第四届国际科创节暨DSC2023国际数字服务大会(数服会)火热筹备。活动定于12月15日在北京举行,主题为:数实...

关键字: DSC TI ST 数字化
[产业新闻]

STL凭借其下一代卢戈夫光纤电缆厂开始"美国制造"

由南卡罗来纳州州长亨利·麦克马斯特(Henry McMaster)阁下揭幕  投资5600万美元  承诺推动美国乡村宽带建设并...

关键字: 光纤 电缆 ST BSP
[产业新闻]

陶朗和Greenpath携手,填补“瓶盖到瓶盖”再生的空白

厦门2023年9月15日 /美通社/ -- Greenpath是美国一家提供全方位综合服务的回收商、加工商和制造商,已有25年的历史,在加利福尼亚州、内华达州和德克萨斯州均设有分公司。Greenpath拥有处理各种物料的...

关键字: GREEN ST 金属 OS
[产业新闻]

浪潮信息Stephen Zhang:大模型时代,我们需要什么样的AI算力系统?

北京2023年9月13日 /美通社/ -- 当前,“百模大战”带来了算力需求的爆发,AI芯片产业也迎来巨大机遇,“创新架构+开源生态”正在激发多元AI算力产品百花齐放。面对新的产业机会,AI算力产业链亟需通过上下游协作共...

关键字: 模型 TE ST AN
[产业新闻]

TECNO携全球首款最小水冷游戏Mini PC主机等参加IFA23

(全球TMT2023年9月8日讯)德国时间9月1日至5日,TECNO征战2023年德国柏林国际消费电子展(IFA)。除了两款首次亮相的TECNO MEGABOOK笔电新品,TECNO还在展会期间发布了两款“全球首创”概...

关键字: TE PC NI ST
[产业新闻]

IFA23回顾-斩获10项国际大奖,TECNO明星产品生态引领科技新标杆

TECNO携两款"全球首创"概念机震撼登场2023德国IFA科技展会 MEGA MINI Water-Cooling游戏主机及PhUltimate卷轴屏引领创新科技新标杆 柏林2023...

关键字: TE NI ST 游戏主机

C语言与CPP编程

249 篇文章

关注

发布文章

厂商专栏

  • 厂商文章

    厂商文章

    5234篇文章
  • 贸泽电子

    贸泽电子

    790篇文章
  • 意法半导体

    意法半导体

    732篇文章
  • ADI

    ADI

    727篇文章
  • 英飞凌

    英飞凌

    455篇文章
  • 是德科技

    是德科技

    315篇文章

热门文章

  • 为什么一打开相机就杀后台?内存和存储为何重要?
  • 人工智能、5G以及物联网,数字化转型的关键动力
  • 打造工业级产品解决方案为何如此重要
  • 又一大厂退出中国大陆半导体制造业务!
  • MP3、DVD等几乎消失!下一个被淘汰的电子产品是什么
  • 华为Pura70可一键消除衣服,华为紧急禁用!
  • 华为发布乾崑ADS 3.0智驾系统 AEB评测全面领先
  • 中软国际两年砍掉2.2万人,此前被曝多次裁员!
  • 裁员换帅!停用中国芯片致出货腰斩?
  • 美国正考虑新一轮制裁,全与这家公司有关!
  • 新一代4D成像雷达性能出众,价格合理
  • 国产自主AI算力!华为:今年将发展超50家鲲鹏和昇腾伙伴
  • 中国工程师在美国被捕:涉嫌TPU/GPU泄密!

编辑精选

更多

论坛活动

  • 泰克全新4系列B MSO,与各种测试挑战say goodbye
    泰克全新4系列B MSO,与各种测试挑战say goodbye
  • 【奖励升级】21ic下载“上传有红包”邀请函,请查收~
  • 欧姆龙器件与模块解决方案,为新能源和高速通信的普及做贡献
  • 全新STM32U0系列MCU来袭, 打造功耗、功能与成本的完美平衡
更多

论坛热帖

  • 十大技术帖
  • 十大生活帖
  • HC32A4A0PITI-LQFP100的AD库(原理,封装)谁能分享我一份,谢谢!
  • 【GD32H757Z海棠派使用手册】第九讲 USART-printf打印实验
  • 如何防止医疗电子SMT贴片加工中BGA开裂
  • 无法进入MHC页面
  • SMT贴片加工技术在医疗设备制造中的应用
  • 马上又到毕业季
  • 求助PIC32MK多电机开发板
  • GD32F427ZGT6 无法进接收中断
  • LKS03x_FOC_OpenSource_Sensorless调试
  • 电机在最高速度运行时波形不对 在低中高速时波形对 请问这是哪里的错误
  • 你藏私房钱吗
  • 如何看待道德绑架
  • 现在就业形势怎么样?
  • 婚前财产公证如何?
  • 某地新娘在闺蜜怂恿下讨要2.88万下车礼无果当场悔婚
  • 终于明白在医院为啥家属有的时候头脑不清醒了
  • 医院陪护有感
  • 上幼儿园总生病怎么办?
  • 人到中年,感谢领导不重用。
  • 麦当劳回应被指使用过期食材:对涉事餐厅带来的影响深表歉意,将进一步加

技术子站

更多

资料下载

  • 华为模块电源管理设计指导-(V100R001_02 Chinese).pdf
  • 电缆载流量计算公式口诀
  • 显示模组 瀚彩1.44+ST7735 SPI演示程序STM32(Keil)
  • 信号完整性基础知识,从仿真到测量
  • 明纬开关电源S-350-27改可调0-95V 0-7A教程
  • 基于CORTEX-M3内核的32位单片机lm3s811的智能开关型电子负载设计论文文档+AD硬件(原
  • spi_rn8302+gd32f30x 三相表程序
  • 抢答器电路图PCB,基于STM32
  • PCB叠层设计的原则和意义
  • 汽车常用ISO15765协议
更多

技术学院

  • TrendForce集邦咨询:夏普堺10代线步入历史,2025年电视面板供应预估将减少近500万片
  • 如何检查氧传感器?氧传感器检查方法详细介绍!
  • 氧传感器坏了有什么表现?氧传感器常见故障有哪些?
  • 氧传感器的工作原理是什么?氧传感器的作用有哪些?
  • PCB电路板翘曲标准是多少?有哪些原因致使PCB翘曲?
  • PCB覆铜有什么作用?PCB干膜有哪些?
  • 什么是PCB扇孔?PCB设计中,对PCB扇孔有什么要求?
  • TrendForce集邦咨询:2024年第一季OLED桌上型显示器出货量约20万台,全年预估134万台,年增161%
关闭
关闭

深圳SEO优化公司张家界网站关键词优化安康网站改版长春百度标王推荐陇南企业网站设计公司永湖设计公司网站推荐南昌网站seo优化报价衡水网站开发哪家好宝鸡网站设计多少钱鹤岗网站设计哪家好张北SEO按天计费多少钱信阳网站改版哪家好吉林模板制作哪家好威海关键词按天收费公司龙岩百姓网标王推广公司濮阳英文网站建设滁州关键词排名公司松原网站建设价格和田关键词按天扣费哪家好民治网站建设公司重庆网站设计报价淮安外贸网站制作价格霍邱网站设计模板多少钱通化百度关键词包年推广公司商洛模板推广哪家好塔城百姓网标王推广公司莆田seo排名哪家好福永关键词按天扣费推荐沧州网站设计模板晋城网站开发公司德阳外贸网站建设报价歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化