源码解析Java数组如何选择排序的算法

1 缘起

源于对排序算法的学习,
算法的学习,绕不过理论学习,同时,学者会自己实现算法,以加深理解,
这是一个阶段,我们所学习的知识,最终是要应用的,
比如,学习了各种排序算法后,大部分学者,要么应对考试,要么应对面试,
在实际的工作中,可能用不到自己实现的算法,或者很少用到,
为什么?
因为,我们的科学巨人,已经将其工程化,久经考验,成为工业级应用工具了,
所以,我们需要进一步看看科学巨匠们是如何应用这些算法的,
这不,从Java的数组排序开始,学习前人的优秀实践,
帮助读者,进一步了解不同的排序算法在软件工业中的应用,提高设计和应对知识考核及交流的能力。

2 前置知识

首先,需要了解时间复杂度、排序算法以及对应的时间复杂度。
其次,分析Java源码中的排序算法,了解不同情况下的排序算法选择。
最后,为自己设计排序算法或者测试时提供参考。

2.1 时间复杂度

时间复杂度用于度量某个程序随输入数据量级的变化运行所消耗时间的变化趋势。
不同时间复杂度的定量趋势(平均时间复杂度),
时间复杂度函数图像如下图所示,
这里横轴表示输入的数据量(如个数),纵轴没有单位(不过,可以使毫秒,秒等),
不同的时间复杂度曲线间对比,纵轴可以不设单位,
只做趋势分析。
由图可知,在数据量不是很多的情况下,
平均时间负载趋势差别不大,
但是,数据量增长到一定数量级后,差距尽现,
大家在使用过程中用,可以实际测试一下。
在这里插入图片描述

2.2 排序算法的时间复杂度

序号排序算法平均时间复杂度稳定性
1冒泡排序 O ( n 2 ) O(n^2) O(n2)稳定
2快速排序 O ( n l o g n ) O(nlogn) O(nlogn)不稳定
3直接插入排序 O ( n 2 ) O(n^2) O(n2)稳定
4希尔排序 O ( n 1.3 ) O(n^{1.3}) O(n1.3)不稳定
5选择排序 O ( n 2 ) O(n^2) O(n2)不稳定
6堆排序 O ( n l o g n ) O(nlogn) O(nlogn)不稳定
7快速排序 O ( n l o g n ) O(nlogn) O(nlogn)不稳定
8归并排序 O ( n l o g n ) O(nlogn) O(nlogn)稳定
9计数排序 O ( n + k ) O(n+k) O(n+k)稳定
10桶排序 O ( n + k ) O(n+k) O(n+k)稳定
11基数排序 O ( n ∗ k ) O(n*k) O(nk)稳定

3 数组排序

Java源码中的数组排序分两大类:
(1)基础数据类型数组排序;
(2)包装数据类型数组排序;
这两类数组排序方法,
都依据排序的数量量级选择不同的排序算法,
以达到较优的计算性能。
注意:这里仅分析不同情况使用的排序算法,并没有深究是如何实现的,我打算在后续的文章中一一分析。

3.1 基础数据类型数组排序

基础数据类型数组排序算法:
依据不同的数据量选择结果如下表所示,从源码中提炼而来,
通过上面的基础知识可知,数据量较少时采用插入排序,平均时间复杂度为 O ( n 2 ) O(n^2) O(n2),数据量较多时采用双端快速排序,平均时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),但是不稳定,当数据量再多时,采用稳定的归并排序,平均时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),至于,阈值,是设计者按照实践得出的较优方案。

序号数据量排序算法
1[2, 47)插入排序
2[47, 286)双端快速排序
3[286, 无穷)归并排序

接下来,看看Java的数组排序源码,
首先进入数组排序入口:sort,
位置:java.util.Arrays#sort(int[])
源码如下图所示,
由源码可知,入口方法调用双轴快速类的排序算法:DualPivotQuickSort.sort,
通过名称可知,该方法使用快速排序,
但是,进入该方法后,可知,不单单使用快速排序,且看下面的介绍。

在这里插入图片描述
好了。进入DualPivotQuickSort类的sort方法,
位置:java.util.DualPivotQuicksort#sort(int[], int, int, int[], int, int)
由源码可知,当排序的数据量小于286时,
使用快速排序:sort,但是,这里同样嵌入了其他排序算法,
数据量大于286时使用 归并排序。
先进入这里的快速排序看看。
在这里插入图片描述

下面进入满足286以内数据的快速排序算法:sort,
位置:java.util.DualPivotQuicksort#sort(int[], int, int, boolean)
源码如下图所示,
由源码可知,这里的快速排序方法同样分分了两层,
当数据量小于47条时,使用插入排序,
大于等于47,小于286时,采用双轴快速排序。

在这里插入图片描述

3.2 包装类型数组排序

和上一节一样,先上结论,如下表所示,
新排序算法:

序号数据量排序算法
1[2, 32)二分法插入排序
2[32, 286)归并排序

旧排序算法:

序号数据量排序算法
1[2, 7)插入排序
2[7, …)归并排序

包装类型数组排序使用的排序方法入口与基础数据类型数组使用不同的方法,
位置:java.util.Arrays#sort(java.lang.Object[])
源码如下图图示,
由源码可知,这里有两个分支,
一个是旧的归并排序,
一个是新的归并排序,默认情况使用ComparableTimSort.sort,类ComparableTimSort复刻TimSort排序思路,
接下来进入该排序方法一探。
在这里插入图片描述

3.2.1 新版归并排序

位置:java.util.ComparableTimSort#sort
源码如下图所示,
由源码可知,
当排序数据量不足两个时,不排序,直接返回,
排序数据量32个以内时,使用二分插入排序,平均时间复杂度 O ( n 2 ) O(n^2) O(n2),稳定,
排序数据量大于等于32时,使用归并排序,平均时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),稳定。
在这里插入图片描述

3.2.2 旧版归并排序

讲完新版排序后,顺便讲一下旧版的归并排序吧。
由名称可知,这是归并排序,
可是,按照聪明的设计者思考,
肯定嵌入了其他的逻辑,先上结果:

序号数据量排序算法
1[2, 7)插入排序
2[7, …)归并排序

插入排序,
位置:java.util.Arrays#mergeSort(java.lang.Object[], java.lang.Object[], int, int, int)
源码如下图所示,
由源码可知,
排序数据量不足7条使用插入排序算法,平均时间复杂度 O ( n 2 ) O(n^2) O(n2),稳定。
其他情况使用归并排序,平均时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),稳定。
在这里插入图片描述

归并排序源码片段如下图所示。
在这里插入图片描述

3.3 设计参考的论文

TimSort论文:https://dl.acm.org/doi/pdf/10.5555/313559.313859

4 小结

4.1 基础数据数组排序算法

序号数据量排序算法
1[2, 47)插入排序
2[47, 286)双端快速排序
3[286, 无穷)归并排序

4.2 包装类型数组排序算法

新排序算法:

序号数据量排序算法
1[2, 32)二分法插入排序
2[32, 286)归并排序

旧排序算法:

序号数据量排序算法
1[2, 7)插入排序
2[7, …)归并排序
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天然玩家

坚持才能做到极致

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值