由浅入深玩转快速排序算法

快速排序可以说是最快的通用排序算法,它甚至被誉为20世纪科学和工程领域的十大算法之一。在众多排序算法中其无论是时间复杂度还是空间复杂度都颇具优势。作为开发工程师,我们很有必要了解它的思想。接下来将由在下为大家一步步分析这一伟大排序算法的原理与实现思路。

主要从三个方向展开论述:

1、算法基本原理与实现:介绍两种常用的实现思路。

2、性能特点:介绍算法高效的秘诀与其弱点。

3、算法优化:介绍PHP7与Java等成熟语言对算法的优化应用

1

快速排序算法基本原理

快速排序是一种分治的排序算法,即将大数组拆分成小数组处理。通过一趟排序将待排数组分成两个子数组,使得一个子数组的元素均比另一个子数组的元素小。再分别将子数组进行上述排序,最终使得整个数组有序。其基本实现可以分为以下3步:

1、选中轴(pivot):从待排序的目标数组中挑选一个元素作为中轴元素。

2、分区(partition):把剩余的元素与中轴元素作比较,将小于等于中轴元素的放到中轴元素的左边,将大于中轴元素的放到中轴元素的右边。

3、 递归:以当前中轴元素的位置为界,将左子数组和右子数组看成两个新的数组并重复上述操作,直到子数组的元素个数小于等于1。

快速排序算法可以有多种实现方式,选择不同的中轴与选择相同的中轴时均有不同的实现方法。下面举出两个例子供大家参考,方便大家理解实现思路。

1.1 左右扫描,交换元素

下图quickSort1是采用了此方案实现的快速排序的一次分区过程,待排序数组是$arr = [4, 3, 8, 1, 6, 2, 7, 5];

1.2 左右扫描,挖坑补坑

在第一个实现思路中,当在左侧找到大于中轴元素和在右侧找到小于中轴元素的两个元素位置时,需要对两个元素进行交换。其中涉及到一个新的临时变量的操作。下面的实现采用填坑的思路直接赋值,省去了临时变量的读写操作。

下图quickSort2是采用了一端挖坑一端补坑的快速排序的一次分区过程,待排序数组同样是$arr = [4, 3, 8, 1, 6, 2, 7, 5];

2

性能特点

快速排序之所以快速主要有两个原因:

1、内循环操作简洁

在分区方法的内循环中仅采用递增或递减的索引将数组元素与一个定值作比较。并且没有在内循环中移动数据,很难想象在排序算法中能有比这操作更简洁的内循环了。

2、比较次数少

快速排序的效率依赖于切分数组的效果,即依赖于中轴元素的值。其最佳的情况是每次都正好将数组对半切分。在这种情况下快速排序的时间复杂度为O(Nlog2N)。而在每个子数组里面的数据也不会与其他子数组的数据做重复比较,大幅提升了效率。

由此我们也很容易看出快速排序的缺点:在分区不平衡的时候可能会出现极低的效率。快速排序最坏情况的时间复杂度为O(N2)。

3

算法优化

3.1 解决分区不平衡

由于中轴元素的选择直接决定了快速排序的效率,为了使算法在数组逆序或将近有序等恶劣场景中都能达到高效率,我们可以采用以下办法解决分区“一边倒”的情况。

1、间接解决:在排序前使数组保持随机性,即先对待排序数组进行乱序操作,再做快速排序,降低分区不平衡的概率。

2、直接解决:采用三数取中法或三取样切分,随机选取中轴元素。

3.2 切换到插入排序

对于小数组排序,插入排序比快速排序更快。因此在排序元素数量较小的数组时应该切换到插入排序。如PHP7的sort()排序函数的实现:在数组长度为 5~16 时采用插入排序否则采用快速排序。

(https://github.com/php/php-src/blob/PHP-7.4/Zend/zend_sort.c)

3.3 三切分快速排序

在实际应用中经常会出现含有大量重复元素的数组,例如我们需要将大量用户数据按VIP等级排序或按生日日期排序。此时采用上述实现方案的经典快速排序则显得有点笨,因为当一个子数组中的元素都是重复时,我们的算法仍然会将它切分成更小的数组递归排序。在有大量重复元素的情况下,这无疑会做很多无用功,使得时间复杂度提高到平方级别(O(N2))。而三向切分快速排序,则是为此而生,专门应对有大量重复元素数组的排序情况,是一种能把时间复杂度从线性对数级别(O(Nlog2N)) 降到线性级别(O(N))的算法实现。

基本实现思路:从左往右扫描数组,利用三个变量$i,$j,$k把数组分成4部分。

如下图所示:

分区刚开始,选择数组最左侧的元素作为中轴元素。$i指向最左侧元素,$k指向最左侧元素的下一个元素,$j指向最右侧的元素。

如下图所示:

从左往右扫描数组,直到$k与$j相交($k > $j)。通过扫描,把未知元素按照其与中轴元素的大小关系放入不同的区间,不断减少未知元素的数量,以完成分区。

当一次扫描结束后$i和$j分别指向了【=$pivot】区间的起始和结束位置。最后分别递归小于中轴部分的数组和大于中轴部分的数组,即可完成排序。

我们对有大量重复数据的数组进行排序,验证三向切分快速排序的效果。(此处的测试仅是为了体现算法实现思想的差异会出现不同的性能效果,并不能严谨说明两者的性能程度差距。)分别用1万,10万和50万数据量的数组进行测试对比,数据生成规则是1-10内随机生成,可以说数据重复概率极高。每个情况进行5次测试求平均值,得出以下数据表格:

可见在有大量重复数据的排序中,三向切分秒杀了经典快速排序。表中所示在对50万级别的数据排序时,经典快速排序耗时高达48分钟(等了好久才等到这个数据),而三向切分快速排序仅用了0.5秒。

而在重复元素较少的数组中,三向切分的性能并无优势,相比经典快速排序需要消耗将近2倍的时间:

是否有一种实现方式既能兼顾有大量重复元素和低重复元素数组的排序呢?接下来,我们看看Java中Arrays.sort()的排序实现。

3.4 双轴快速排序

在JDK1.7中给出了双轴快速排序(DualPivotQuicksort)的思想,当然仅靠一个排序思想无法应对复杂的业务场景,为保证最高的排序效率,Java在实际排序中采用了一套相对成熟及复杂的方案,根据元素的数量及有序性采用了不同的排序方案。

双轴快速排序在有大量重复元素的排序中表现良好,同时能兼顾大量非重复元素的数组排序,其在实现思路上,跟三向切分快速排序有类似之处。已经理解了上面介绍过的三向切分,再来理解双轴快速排序就不难了。大致实现思路如下图所示:

完成分区后,分别对双轴切分出来的三个区间进行递归排序即可。(受限于篇幅,以上所有算法实现只讲述思路并未贴出代码,如感兴趣的同学可以找作者要PHP版的实现代码。)

总结

本文主要讲述了快速排序的原理及实现思路,总结为以下三点:

1、基本原理与实现:可采用元素交换或挖坑补坑的方式实现快速排序。

2、算法性能特点:

  • 快速高效的两个原因:其一内循环操作简洁;其二比较次数较少

  • 性能弱点:中轴元素选择不当可能导致性能极其低下

3、算法优化:

  • 解决分区不平衡的2种办法

  • 对小数组排序采用插入排序

  • 对有大量重复元素的数组采用三向切分快速排序

  • JDK1.7中双轴快速排序的实现思路

参考文献:

[1]《算法(第4版)》

[2]《QUICKSORTING - 3-WAY AND DUAL PIVOT》https://rerun.me/2013/06/13/quicksorting-3-way-and-dual-pivot/

[3]《Java中双基准快速排序方法的具体实现》http://www.mamicode.com/info-detail-2395124.html

排版 |川芮

快速排序 挖坑_由浅入深玩转快速排序算法相关推荐

  1. 根据坐标如何标记图片_如何玩转FloodFill算法?

    读完本文,你可以去力扣拿下如下题目: 733.图像渲染 ----------- 啥是 FloodFill 算法呢,最直接的一个应用就是「颜色填充」,就是 Windows 绘画本中那个小油漆桶的标志,可 ...

  2. 快速排序伪代码_数据结构和算法之快速排序

    快速排序是一种有意思的排序算法.下面我们从核心思想,图解,递推公式,优化和性能度量五个方面讲讲快速排序. 快排的核心思想是这样的 -对于排序数组中下标为p......r之间的一组数据,我们选择p到r的 ...

  3. java 概率 算法_使用概率算法优化快速排序(JAVA)

    前言 前面一篇文章系统介绍了快速排序算法,提到快速排序虽然平均时间复杂度为o(n*log2(n)),效率相对比较高.但是其在特殊情况下,比如降序的情况下,效率和冒泡排序一致,这就削弱了快速排序给人的好 ...

  4. java 部分正确性_深入理解java快速排序的正确性

    说起快速排序,很多人都能够写出一个正确的快速排序,但就快速排序的正确性,就无从探究了.为什么说写出来的快速排序就是正确的.在快速排序中间的关键几步,以什么样的数据组织来保障快速排序的正确性.本文以&l ...

  5. 快速排序c语言单链表代码,快速排序算法及源代码(C语言)

    快速排序是对冒泡排序的一种改进.它的基本思想是:通过一躺排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一不部分的所有数据都要小,然后再按次方法对这两部分数据分别进行快速排序,整个排 ...

  6. 快速排序--挖坑法详解

    一,思想描述: 何谓挖坑法?我们来看一看详细过程. 给定原始数列如下,要求从小到大排序: 首先,我们选定基准元素Pivot,并记住这个位置index,这个位置相当于一个"坑".并且 ...

  7. k均值算法 二分k均值算法_如何获得K均值算法面试问题

    k均值算法 二分k均值算法 数据科学访谈 (Data Science Interviews) KMeans is one of the most common and important cluste ...

  8. ourplay下载-ourplay官网版(原谷歌空间)_易玩网

    ourplay下载-ourplay官网版(原谷歌空间)_易玩网 http://www.yiwan.com/soft/637795/

  9. 保姆级人工智能入门攻略,谁都能玩的AI算法!

    哈喽,大家好,我是 Jack. 上期视频<6分钟带你回顾2020年高能的AI算法>播放量近40w,引来了很多小伙伴的关注. 那今天就聊一聊,怎么玩转这些高能的AI算法,以及如何入门人工智能 ...

最新文章

  1. JavaWeb笔记01
  2. R语言使用ggplot2包使用geom_boxplot函数绘制基础分组箱图(不同分组配置不同的箱体填充色)实战
  3. 微软发布WP SDK8.0 新增语音、应用内支付等原生API
  4. opencv 叠加两张图_「干货」教你如何用OpenCV快速寻找图像差异处
  5. 测试SAP云平台上的Redis数据库服务
  6. sqlalchemy通过已经存在的表生成model的方法
  7. python从大到小排序_python作业:用嵌套的列表存储学生成绩数据,并编程完成如下操作...
  8. 远程下载马bypass waf
  9. Linux网络编程和套接字
  10. beforeunload中阻止提示关闭_React 系统中,在离开编辑页面前做提示
  11. DSSM,Deep Structured Semantic Models
  12. java velocity 语法_Velocity初探小结--velocity使用语法详解
  13. 【测试能力提升】Jira 和禅道数据库分析,方便你写周报、写总结、出报告
  14. 宽带连接蓝屏,或者显示813占用端口
  15. Docker系列 深度使用nextcloud(一)
  16. 利用ps制作油画风格的照片
  17. python做ui界面_从零开始创建UI
  18. 七个不良习惯导致攻击者轻松入侵数据库
  19. 亚马逊卖家,为什么你做不出爆品?从动森看大火之道
  20. php安装失败,phpcms安装失败怎么办

热门文章

  1. 32和64位jvm_我应该使用32位还是64位JVM?
  2. Java注释是一个大错误
  3. maven 版本号插件_Maven内部版本号插件–用法示例
  4. jax-rs jax-ws_JAX-WS入门
  5. spring ldap_Spring 3,Spring Web Services 2和LDAP安全
  6. 都灵JVM编程语言:使用ANTLR构建高级词法分析器
  7. 将数据压缩到数据结构中
  8. 如何在Java中读取CSV文件-Iterator和Decorator的案例研究
  9. Spring MVC:资源
  10. Javaone 2013评论