双枢轴快排算法
Vladimir Yaroslavskiy
第一版:2009-02-16
最后更新:2009-09-11
译者:黑狗 Email:blackdog@gmail.com
介绍
在计算机科学中数据排序是最基础的一个问题,尤其是数组对象是原生的,例如整型,字节,浮点等等。由于排序方法在计算机系统和其他数据处理系统中扮演了重要的角色,我们对寻找比现存算法更好的算法就显得兴趣十足。我们使用了大量的开销最大的操作来比较排序算法。这些操作(比较和交换)的效率受排序技术的影响。快排算法是一个高效的,被广泛使用的排序过程,他需要C*n*ln(n)次操作。这里的n指数组的大小。问题关键在于找到一个最小系数C的算法。这里有许多尝试用于改进快排算法中的古典不变式:
1. 从数组找找到一个元素,成为枢轴
2. 重新排列元素,使小于枢轴的元素置于前,大于的置于后(相等的可以在任何一边)。在这样操作以后,枢轴元素就落到了他的最终位置。
3. 递归的处理大于和小于枢轴的子数组。
Hoare,Helman,Knuth,Sedgewick以及其他的一些科学家将大多数精力都花费在具体的“划分和优胜”算法实现。或者是尝试根据特定的枢轴元素的选择来提高性能。但他们都使用了古典的划分方案——将枢轴划分为两个部分。
我们可以向大家展示使用两个枢轴元素(或者说将数组划分为三部分)是更加高效的,特别是在大数组的情况下。我们推荐一种新的算法——双枢轴快排方案。在这种情况下,他比已知的这些实现方案更快。双枢轴快排算法通过不同的输入和原生数据类型来进行研究。和古典的快排算法,以及JDK6.0平台上的快排实现比起来,他在交换操作上是有优势的。
新的双枢轴快排算法
新的快排算法用两个枢轴元素P1和P2将数组T[]a划分为三个部分。(因此,他需要三个指针L,K,G,和left,right——用于标记递归的首尾数组)这里的T指代的是原生数据类型(例如整型,浮点,字节,字符,双精度,长整型和短整型)。正如Figure 1所示:
这个算法执行以下的步骤:
1. 对于小数组(长度小于27),使用插入排序。
2. 选择两个枢轴元素P1和P2。比如我们可以使用第一个元素a[left]作为P1,a[right]作为P2。
3. P1必须小于P2,否则交换。因此,他被分为了一下几个部分
a) Part I使用使用标记left+1到L-1的元素。他们小于P1
b) Part II使用标记L到K-1的元素。他们大于等于P1,小于或等于P2
c) Part III使用标记G+1到right-1。他们大于P2
d) Part IV使用标记K到G,包含剩下的需要检查的元素
4. Part IV中的下一个元素a[K]和数周元素P1和P2进行比较,记录相应的part I,II和III的位置。
5. 指针L,K和G根据相应的指向而变换
6. 当K小于等于G时重复执行4-5
7. 枢轴元素P1和Part I的尾部元素交换,P2和part III的首部元素交换
8. 对于part I,II和III递归调用,重复执行1-7部
数学证明
双枢轴快排的平均比较次数为2*n*ln(n),平均交换次数为0.8*n*ln(n)。而古典快排算法分别为2*n*ln(n)和1*n*ln(n)。完整的数学研究已经完成,并将拷贝到此处(待定)。(坑死爹!找了个没完成的论文!次凹!谁给我提供一个链接啊!)
比较和总况
新的双枢轴快排算法拥有以下的优势:
l 对于原始数据类型,将数组划分为三部分比古典的方法更高效。和古典快排,JDK6中的快排比起来,数组越大新的算法在比较上的性能就越好。例如,我们采用了如下的数据量进行试验:2000000个随机整型元素分别使用双枢轴,古典和JDK6快排执行50次,并分析其总时间。他们分别花费16.5,,1.9和20.3秒。对于整型的双枢轴方法可以被简单的调整后,完成另一种数值,字符串和其他可比较的类型。
l 我们所推荐的双枢轴方法在古典的指定采样数组和重复元素数组的表现也更加快捷。在这些情况下,双枢轴快排在执行了所有的测试情况之后,比JDK6快排快。双枢轴耗费55,JDK6耗费100。
l 这种算法对于特殊的过程中,通过枢轴元素P1,P2的选择还有其他的改进。我们不选取a[left]和a[right]元素,而是选择两个中间元素。在任意数据源的情况下,这种修正并没有使双枢轴快排的属性更糟糕:
Int third = arrayLength / 3;
P1 = a[left + third];
P2 = a[right - third];
我们可以将这种算法的特征归纳如下:
l 省时
l 采用“划分和优胜”策略
l 采用双枢轴而不是单枢轴
l 可用于数据分析的更搞笑的排序过程
l 双枢轴算法将会被推荐在下一个版本的JDK发布版中。
JAVA编程语言的实现
public static void sort(int[] a){
sort(a,0,a.length);
}
public static void sort(int[] a, int fromIndex, int toIndex){
rangeCheck(a.length, fromIndex, toIndex);
dualPivotQuicksort(a, fromIndex, toIndex – 1, 3);
}
private static void rangeCheck(int length, int fromIndex, int toIndex){
if (fromIndex > toIndex) {
throw new IlleagalArgumentException(“fromIndex > toIndex”);
}
if (fromIndex < 0) {
throw new ArrayIndexOutOfBoundsException(fromIndex);
}
if (toIndex > length) {
throw new ArrayIndexOutOfBoundsException(toIndex);
}
}
private static void swap(int[] a, int I, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
private static void dualPivortQuicksort(int[] a, int left, int right, int div) {
int len = right – left;
if (len < 27) { // insertion sort for tiny array
for (int i = left + 1; i <= right; i++) {
for (int j = i; j > left && a[j] < a[j – 1]; j--) {
swap(a, j, j - 1);
…………….(代码就不贴了,大家百度一下或者看JDK 7 Arrays.sort就是了,搞不懂在论文里写那么全的代码做什么…)
实验结果
我们已经进行了大量的测试,这里是执行的时间:
Server VM:
...
后面的就不译了,都是引用神马的
附上我找到的原文链接,坑死爹啊,求人帮我找官方一点的论文,我没找到啊,又不好意思给作者email。这一篇一点都不严谨!比JDK6的快排论文差太远了!