快速排序是一种相当棒的排序方案,相关理论内容可以参见快速排序(一) 原理介绍
在jdk的[java.util.Arrays]类中,有一个sort的函数,它实现对很多数据结构进行的排序方法,其中sort(int[] a)中主要使用的是优化后的快速排序法,本文正是基于此来讲解如何优化快速排序算法。
java源代码:
★ 优化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}
仔细想想,对于重复元素较多的数组,这种优化无疑能到达更好的效率。