(1) 基本数据类型数组的排序,如Arrays.sort(int[])等。采用了一种经过调优的快速排序。该算法改编自 Jon L. Bentley 和 M. Douglas McIlroy 合著的 Engineering a Sort Function", Software-Practice and Experience Vol. 23(11) P. 1249-1265 (November 1993)。此算法在许多数据集上提供 n*log(n) 性能,这导致其他快速排序会降低二次型性能。
下面是JDK中调优快速排序算法的源代码:
★ 优化1:在小规模(size<7)数组中,直接插入排序的效率要比快速排序高。
没有一种排序在任何情况下都是最优的《基于比较的内部排序总结》。O(N^2)级别的排序看起来似乎比所有先进排序要差的多。但实际上也并非如此,Arrays中的sort()算法就给了我们一个很好的例子。当待排数组规模非常小的时候(JDK中规模的阈值为INSERTIONSORT_THRESHOLD=7),直接插入排序反而要比快排,归并排序要好。
这个道理很简单。数组规模小,简单算法的比较次数不会比先进算法多多少。相反,诸如快排,归并排序等先进算法使用递归操作,所付出的运行代价更高。
★ 优化2:精心选择划分元素,即枢轴。
快排有一种最差的情况,即蜕化成效率最差的起跑排序(见《交换排序》)。导致这种情况产生的主要原因就是枢轴的选择并不能把整个数组划分成两个大致相等的部分。比如对于基本有序的数组,选择第一个元素作为枢轴就会产生这种蜕化。
既然如此,我们可以看看Arryas.sort()是如何为我们选择枢轴的。
● 如果是小规模数组(size<=7),直接取中间元素作为枢轴。
● 如果是中等规模数组(7=<size<=40),则在数组首、中、尾三个位置上的数中取中间大小的数作为枢轴
● 如果是大规模数组(size>40),则在9个指定的数中取一个伪中数(中间大小的数s)
中小规模时,这种取法尽量可以避免数组的较小数或者较大数成为枢轴。值得一提的是大规模的时候,首先在数组中寻找9个数据(可以通过源代码发现这9个数据的位置较为平均的分布在整个数组上);然后每3个数据找中位数;最后在3个中位数上再找出一个中位数作为枢轴。
仔细想想,这种精心选择的枢轴,使得快排的最差情况成为了极小概率事件了。
★ 优化3:根据枢轴v划分,形成一个形如 (<v)* v* (>v)* 的数组
普通快排算法,都是使得枢轴元素移动到数组的较中间位置。枢轴之前的元素全部小于或等于枢轴,之后的元素全部大于枢轴。但与枢轴相等的元素并不能移动到枢轴附近位置。这一点在Arrays.sort()算法中有很大的优化。
我们举个例子来说明Arrays的优化细节 15、93、15、41、6、15、22、7、15、20
第一次枢轴:v=15
阶段一,形成 v* (<v)* (>v)* v* 的数组:
15、15、7、6、41、20、22、93、15、15
我们发现,与枢轴相等的元素都移动到了数组的两边。而比枢轴小的元素和比枢轴大的元素也都区分开来了。
阶段二,将枢轴和与枢轴相等的元素交换到数组中间的位置上
7、6、15、15、15、15、41、20、22、93
阶段三,递归排序与枢轴不相等都元素区间{7、6}和{41、20、22、93}
仔细想想,对于重复元素较多的数组,这种优化无疑能到达更好的效率。
(1) 对象数组的排序,如Arrays.sort(Object[])等。采用了一种经过修改的归并排序。其也有几个优化的闪光点。
下面是JDK中改进归并排序算法的源代码:
★ 优化1: 同上面的快速排序
★ 优化2: 如果低子列表中的最高元素小于高子列表中的最低元素,则忽略合并。这个优化措施无疑对基本有序序列是极大的效率改进。