双轴快速排序(Dual Pivot Quicksort)

引子

在Timsort的分析前,我提到了在使用Arrays.sort()时针对int[]会使用DualPivotQuicksort类进行排序。实际上针对非对象类型,都会使用这个方法。JDK1.7开始使用双轴快速排序进行排序。

双轴快速排序的特点主要有:

  • 比传统快排更快。
  • 分治思想。“divide and conquer”
  • 使用两个轴pivot而不是一个。

下文会具体介绍。

另外值得一提的是,JDK 1.8中DualPivotQuicksort并没有完全使用双轴快速排序的做法,而是按照基本数据类型和数组长度,用了快速排序、计数排序、归并排序:)最后我会分析源码加以说明。

一般快速排序的思想

对于排序算法来说,最耗时的部分一般来说为两种:比较交换。当然这也是排序算法的核心步骤。

We compare sorting methods by the number of the most “expensive” operations, which influence on effectiveness of the sorting techniques, — comparisons and swaps. Quicksort algorithm is an effective and wide-spread sorting procedure with Cnln(n) operations, where n is the size of the arranged array. The problem is to find an algorithm with the least coefficient C.

参考Dual Pivot快排原始论文,研究新的快速排序方法,目的在于降低复杂度中的常数C。

一般的快速排序步骤如下:

  1. 从要排序的列表中选择一个元素,称作轴(这个翻译有点神奇)pivot。
  2. 整理列表,使得轴左侧的元素均小于pivot,轴右侧的元素均大于pivot。(其中等于的放在左右侧均可)这个过程称为partition,结束后轴元素便确定了其最终的位置。
  3. 递归的使用1、2的步骤来排序轴左侧和右侧的两个子序列。

一开始针对这个算法的优化大多有两种,一种是优化“divide and conquer”算法的实现,另一种是试图通过选取特定的轴pivot元素,来改善性能。但这里他们都是基于二分区,也就是一个轴的方式。

Vladimir Yaroslavskiy等人的研究提出了一个双轴的方式(two pivot,or partitioning to three parts),并证明了针对长度较大的序列是一种更有效的方式。

双轴快速排序步骤

首先定义几个变量。我们要排序的原始序列T[] a,T代表一个原始类型(int,float,byte,char,double,long,short),也就是非对象并且可比较的类型。

P1、P2为选择的两个pivot轴,其指针位置用leftright变量表示。

我们再定义三个指针LK和**G,**其中位置见下图。三个指针区分出了四个分区。

这里我直接使用原始论文的图片了哈,画的很清楚了:)原始论文

双轴快速排序的分区

上图表示在算法过程中的某一个时刻的部分。算法步骤如下:

  1. 对于小序列,长度小于27的序列,使用插入排序。(后文JDK 1.8中使用的是47)
  2. 选择两个轴元素P1、P2。例如图中选择第一个元素a[left] = P1,最后一个元素a[right] = P2。
  3. 我们限定P1 < P2(如果不是,则交换P1、P2)。这样我们就可以分出如下部分:Part I [left, L - 1]为< P1部分,Part II [L, K - 1]为 >= P1且<= P2部分,Part III [G + 1, right - 1]为> P2部分,Part IV [K, G]为还没有确定的部分。
  4. 针对当前Part IV中的a[K],与P1和P2比较,比较后放入Part I II III中的一个。
  5. 调整L、K、G到适合的位置。
  6. 重复4、5步直到K > G。也就是说PartIV的元素全部分散到Part I II III中,最后是三个Part。
  7. 将P1放入PartI的最后一个位置,P2放入PartIII的前一个位置(或者第一个位置)。这样就确定了P1和P2的位置。
  8. 对于PartI II III ,重复1-7步。

性能与优势

经过证明,比较的平均次数为2nln(n)次,平均交换次数为0.8nln(n)。比普通快排要好。

It is proved that for the Dual-Pivot Quicksort the average number of comparisons is 2*nln(n), the average number of swaps is 0.8*nln(n), whereas classical Quicksort algorithm has 2*nln(n)* and 1*nln(n)* respectively.

对比JDK6.0的快排:

实验表明在随机分布的序列中,需要排序的序列长度越大,双轴快排的性能领先越明显。

双轴快排在非随机序列和有重复元素的序列中表现也不错。

在选择P1、P2双轴时,不使用第一个位置和最后一个位置,而是使用中间的两个元素,例如:

int third = arrayLength / 3;
P1 = a[left + third];
P2 = a[right - third];

这样的性能也会有额外的提升,并且在随机排列的序列中表现也没有下降。

JDK 1.8 DualPivotQuicksort源码分析

点开源码,我们发现这个类中有几个静态变量:


Tuning Parameters

看一下英文说明不难发现,这里的意思是根据array的不同长度,选择不同的排序方法。这里的run和Timsort中run是一个含义,代表升序或降序的序列(此处建议先理解一下什么是Timsort中的Run)。

这里一个有趣的事情是,JDK针对七种数据类型实现了七个方法。(当然这里主要是因为方法参数是不同的,没法写到一起),当然有一些细节还是有区别的。比如我们知道,char的范围是很小的,当array长度很大时,这种分布方式恰好使用计数排序是更快的。

当然这里不失一般性,我们还是使用int整数类型来理解排序过程,其他类型感兴趣的读者,自己看源码吧:)。int类型的sort步骤如下:

  1. 长度小于QUICKSORT_THRESHOLD,使用快排。
  2. 判断run个数,当run个数满足大于MAX_RUN_COUNT时,或相同的相连元素个数大于MAX_RUN_LENGTH时,使用快排。(说明数据分布偏随机化,或相等的相连元素比较多。)
  3. 根据2中已经分好的run,使用归并排序。

快排的步骤如下:

首先我们知道快排方法实现时是一个递归调用的过程,在下文中由于有条件判断,“走快排”或“走双轴快排”,我是特指当前调用过程中,分part的过程,分完part之后针对每个part会重新调用一次快排,此时还需要重新判断,而不是就“永远走双轴快排”了。因为每一个part条件会变。

  1. 长度n小于INSERTION_SORT_THRESHOLD,走插入排序。
  2. 取 m = n / 7,找到序列中间数e3,向左偏移和向右偏移m个分别两次,也就是一共取五个位置数e1,e2,e3,e4,e5。使用插入排序将这五个位置数排序。
  3. 如果2中五个数排序后相邻的两两不相同a[e1] != a[e2] && a[e2] != a[e3] && a[e3] != a[e4] && a[e4] != a[e5],则取a[e2]和a[e4]作为P1和P2,走双轴快排。否则取pivot = a[e3],走普通的快排。

值得一提的是,这里的普通快排也是走三向的,中间部分是等于。看下文注释截图就好了。

双轴快排和上文描述基本一致。

普通快排中间part是==pivot部分。

双轴快速排序(Dual Pivot Quicksort)相关推荐

  1. 单轴快排(SinglePivotQuickSort)和双轴快排(DualPivotQuickSort)及其JAVA实现

    快速排序使用的是分治思想,将原问题分成若干个子问题进行递归解决.通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快 ...

  2. 浅谈Java底层排序双轴快排

    在Java 底层的排序中有几个非常有用并且面试可以装逼的算法,TimSort,DualPivotQuicksort( 双轴快速排序 ),( binarySort)二分插入排序. 这三个排序,TimSo ...

  3. 双枢轴快速排序与 Arrays.sort()

    时间复杂度差距 当数据量大起来的时候可以看到n*log(n)以及n2,n3,2^n的时间复杂度图像都已经快成竖线了.所以在大数据面前你还在写两个for的n平方时间复杂度的暴力解法算法吗? Arrays ...

  4. 【排序算法】——图解双轴快排(建议收藏)

    原创公众号:「bigsai」,转载需注明出处 关注回复bigsai领取Java进阶pdf,回复进群加入力扣打卡群(目前200+). 觉得不错还请一键三连! 前言 在排序算法中,快排是占比非常多的一环, ...

  5. 排序算法--快速排序(QuickSort)、 3区快速排序(3 Way QuickSort)原理、适用场景及代码示例

    快速排序 概念介绍 QuickSort快速和归并排序一样,是采用分治法解决问题的一个典型应用.它选择一个元素作为基准元素,并围绕选定的基准元素对给定数组进行分区. quickSort有很多不同的版本, ...

  6. Java 细节汇总(4)-Arrays 中的双轴快排

    文章目录 1. Arrays 中的双轴快排 2. Java 中 switch 支持字符串的原理 3. Java 中 break,continue 标签的用法 4. Java 中 Math.ceil() ...

  7. 舵机任意角度程序_真香!!!飞特发布性价比超高的19kg磁编码360°双轴串口总线舵机STS3215...

    2020年4月6日,深圳飞特模型有限公司发布了2020年新款磁编码版本的TTL串口总线舵机.这款舵机是基于SCS215电位器版本开发的更高性能的磁编码版本,不仅具备了飞特SM高端系列的高性价比功能,又 ...

  8. 双轴机械臂串口控制命令开发与测试:STM32F103控制板,简易调试命令集合

    ▌01 底层串口控制命令 1.调试说明 本文是继 调试机械臂一体化控制电路 博文中对于 两轴机械臂+机械爪整体控制板设计与机械爪控制调试 在 基于STM32F103双轴机械臂完整电路板 控制下进行串口 ...

  9. 组装肩部带有减速器双轴机械臂组装与调试

    ➤ 00背景 在 增加了机械爪的双轴机械臂 安装调试之后, 发现进行平顺控制效果不好 ,因此在原来的基础上进行了如下的改动: 肩部和肘部的角度传感器采用了: 角度编码器 ST-3806-15-RS 读 ...

最新文章

  1. CVPR2019论文看点:自学习Anchor原理
  2. mysql中 课程1比课程2成绩高_小菜菜mysql练习解读分析1——查询 01 课程比 02 课程成绩高的学生的信息及课程分数......
  3. WPF 与Surface 2.0 SDK 亲密接触 - ScatterView 数据绑定篇
  4. 10停止nginx命令 win_Linux下配置Nginx并使用https协议
  5. 使用rsync工具构建php项目管理平台
  6. java游戏细菌_细菌Bacteria
  7. 做业务千万不要把鸡蛋放在一个篮子里
  8. 我儿子竟跟男孩子抱在一起
  9. [Usaco2007 Demo]City skyline
  10. DirectX12(D3D12)基础教程(十八)—— PBR基础从物理到艺术(中)
  11. Flash版Logo语言9.83
  12. Netty 解决TCP粘包/半包使用
  13. php api 实例maccms,api.php · do do/maccms10 - Gitee.com
  14. java 大小写转换函数_java字符串大小写转换的两种方法
  15. 大数据技术之HFDS
  16. java 输出乘法口诀第一列_java输出乘法口诀表
  17. 测试用例的设计方法及案例
  18. c++如何批量修改文件后缀名
  19. 农历类==解析指定的日期 1900-2100
  20. 不要再走弯路了,黑客学习路线看这里

热门文章

  1. 不朽凡人 正文 第四百五十三章 天仙领域
  2. html修改div里的图片,html 如何将div 里面的图片设置与div等宽,没有缝隙?
  3. 妙笔生画:用desmos在线绘制y=f(x)或z=f(x,y)数学函数图像
  4. qt实现的五子棋小游戏(Qpainter)
  5. linux testdisk使用教程,江湖救急!磁盘数据大救星TestDisk
  6. 推荐14款最受欢迎的3d建模软件
  7. Charls抓包工具使用
  8. Charls免费注册
  9. 40年的努力,人类基因组测序终于完成
  10. 利用QR算法求解矩阵的特征值和特征向量