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

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

      快速排序可以说是最快的通用排序算法,它甚至被誉为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];

8ebc18604a5e918d16cbac21e5c48a9f.png

1.2 左右扫描,挖坑补坑

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

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

b2df6c94dacd3f36e62ad234374356d6.png

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))的算法实现。

8de4fb5d02567ddb43f65d563ab2f835.png

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

如下图所示:

dc66714d6c183069545febaea25d1fef.png

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

如下图所示:

139c55b48a106572ec1a059b6fcfa49f.png

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

fdcd45f0732a3d213b71f4899728d61c.png

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

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

40721f4dd79823bfae62b60be2508574.png

56c4f5b311bd7a182c2cf4307dd38e67.png

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

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

64af5e23dc0664f574e288da7172eb3c.png

26a53e3fc906ecd09b79bc75693d18ec.png

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

3.4 双轴快速排序

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

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

2597478eb35982e7d360d3698bc3219a.png

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

排版 |川芮

e24dc1d64d921bd620d34a3ebf511f88.gif

0b78eade6eafee86c3ebfe44169ed1c0.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/340599.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码拾取图片某一点的颜色_RPG游戏开发日志7:道具拾取与存放

本项目同步上传于github和coding上,国内读者可以通过在coding下载项目。也欢迎你加入我的UE4学习交流QQ群:872537977。如果你喜欢我写的文章,也希望你点赞、收藏、转发。谢谢!如果你喜欢我写的文章,也希望你点赞、收藏…

abp vue如何配置服务地址_DHCP服务如何配置才能尽量减少被攻击的可能

DHCP Snooping是啥?DHCP Snooping是DHCP的一种安全特性,用来保证DHCP客户端能够正确的从DHCP服务器获取IP地址,防止网络中针对DHCP的攻击。DHCP Snooping是如何防止DHCP攻击的呢?DHCP,动态主机配置协议,在I…

通达信缠论买卖点公式_通达信缠论多空主图指标公式

1.高位无量就拿,就算拿错了也要拿着。高位就是说股价处于或者接近历史的最高位。高位无量横盘,是非常典型的上涨中继形态之一,高位的窄幅箱型振荡。2.高位放量出现就要跑,哪怕是跑错也要跑。股价在已经过了一段时间比较大的涨幅后&#xff0c…

多节锂电串联保护板ic_如何有效保护锂电池板,一款优质的MOS管就能解决

锂电池几乎应用于我们日常接触到的各类电器之中,但如何保护锂电池,你又是否知道呢?其实在锂电池保护板,最主要的元器件是IC与MOS。MOS对锂电池板的保护作用非常大,它可以检测过充电,检测过放电,…

使用pp架构形成计算机集群请求的地址无效_干货!史上最详细脑图《大型网站技术架构》...

1. 介绍一下《大型网站技术架构》这本书可能很多人都看过,小编个人觉得真的是非常不错的一本书。看完这本书后,你会对如何设计大型网站架构,有非常清晰的思路。如果还没有读过的小伙伴,赶紧去读一读吧。PS:小编这里有这…

泰坦尼克号数据_数据分析-泰坦尼克号乘客生存率预测

项目背景目标预测一个乘客是否能够在泰坦尼克号事件中幸存。概述1912年4月15日,泰坦尼克号在首次航行期间撞上冰山后沉没,船上共有2224名人员(包括乘客和机组人员),共有1502人不幸遇难。造成海难失事的原因之一是乘客和…

linux mysql服务器安装_Linux服务器MySQL安装

Linux服务器MySQL安装1. MySQL官网下载如图:2. 安装MySQL[rootiZ2zebb0428roermd00462Z /]# rpm -ivh https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch#过滤MySQL:[rootiZ2zebb0428roermd00462Z /]#yum repolist all |grep mysql#ena…

python3 array为什么不能放不同类型的数据_来自俄罗斯的凶猛彪悍的分析数据库ClickHouse...

点击上方蓝色字体,选择“设为星标”回复”资源“获取更多资源大数据技术与架构点击右侧关注,大数据开发领域最强公众号!暴走大数据点击右侧关注,暴走大数据!ClickHouse相关文章推荐:战斗民族开源 | ClickHo…

mysql锁表查询_Mysql数据库锁情况下开启备份导致数据库无法访问处理分享

[背景简介]MySQL是一种开放源代码的关系型数据库管理系统(RDBMS),因为其速度、可靠性和适应性而备受关注。大多数人都认为在不需要事务化处理的情况下,MySQL是管理内容最好的选择。mysql虽然功能未必很强大,但因为它的开源、广泛传播&#xf…

mysql直接执行文件格式_Windows 环境下执行 .sql 格式文件方式

windows 命令行中有2种执行 .sql 文件的方式:直接行文件 和 先进入mysql命令行然后执行文件。具体操作如下:1. 直接在windows命令行执行。打开windows命令行(winR打开运行窗口然后输入cmd,回车),进入mysql的本机地址,如果配置了环…

Java大数据处理的流行框架

大数据挑战 在公司需要处理不断增长的数据量的各个领域中,对大数据的概念有不同的理解。 在大多数这些情况下,需要以某种方式设计所考虑的系统,以便能够处理该数据,而不会随着数据大小的增加而牺牲吞吐量。 从本质上讲&#xff0c…

带有Prometheus的Spring Boot和测微表第6部分:保护指标

以前,我们使用Prometheus成功启动了Spring Boot应用程序。 Spring应用程序中的一个端点正在公开我们的指标数据,以便Prometheus能够检索它们。 想到的主要问题是如何保护此信息。 Spring已经为我们提供了强大的安全框架 因此,将其轻松用于…

使用AWS Elastic Beanstalk轻松进行Spring Boot部署

朋友不允许朋友写用户身份验证。 厌倦了管理自己的用户? 立即尝试Okta的API和Java SDK。 在几分钟之内即可对任何应用程序中的用户进行身份验证,管理和保护。 几乎所有应用程序都依赖于身份验证。 开发人员以及雇用他们的公司都想确认谁在发出请求&…

mysql报错乱码_连接mysql服务器报错时,出现乱码

页头用了header(content-type:text/html;charsetutf-8);try{$this->dbonew PDO($dsn,$dbuser,$dbpassword);}catch(Exception $e){echo $e->getMessage();}连接失败时会报错,但是乱码,IE下编码查看是UTF-8,但是是乱码,如果选…

zookeeper 负载_ZooKeeper,策展人以及微服务负载平衡的工作方式

zookeeper 负载Zookeeper如何确保每个工人都能从工作委托经理那里愉快地完成工作。 Apache ZooKeeper是注册,管理和发现在不同计算机上运行的服务的工具。 当我们必须处理具有许多节点的分布式系统时,它是技术堆栈中必不可少的成员,这些节点…

高效的企业测试-集成测试(3/6)

本系列的这一部分将展示如何通过代码级以及系统级集成测试来验证我们的应用程序。 (代码级)集成测试 集成测试一词有时在不同的上下文中使用不同。 根据Wikipedia的定义,我指的是在代码级别上验证多个组件之间相互作用的测试。 通常&#x…

带Prometheus的Spring Boot和测微表第4部分:基础项目

在以前的文章中,我们介绍了Spring Micrometer和InfluxDB。 所以你要问我为什么普罗米修斯。 原因是Prometheus在InfluxDB的拉模型与推模型上进行操作。 这意味着,如果将千分尺与InfluxDB一起使用,则在将结果推送到数据库中时肯定会有一些开…

前端如何实现网络速度测试功能_分析Web前端测试要点,从架构原理上进行分析,希望大家能够掌握...

基于Web前端分析过程,大概有十几个测试要点,我们今天主要来讲解结合前五个要点进行详细解说。前端测试点主要针对前端展开,什么叫前端分析呢?就是我们所有的分析和测试要点所站的视角都是针对客户端或者浏览器来对系统进行分析和测…

将Websocket与Spring Framework和Vuejs结合使用

Websocket是客户端和服务器之间的全双工(持久)连接,因此两者可以彼此共享信息,而无需重复建立新的连接。 这消除了从客户端重复轮询以从服务器获取更新的需要。 并非所有浏览器都支持Websocket,因此我们利用SockJS ja…

python函数和模块的使用方法_Python学习06_函数和模块的使用

引入在写有些代码的时候,会发现有些步骤重复了多次,他也不像循环,都是相同的东西在重复,而是指做某件事情的步骤方法,做事的人或对象发生了改变,但是方法却没有改变。要想写出高质量的代码,首先…