Java SDK中的sort算法小议 - 03 双轴快排
Java SDK中的sort算法小议 - 03 双轴快排
有了之前分析单轴快排
的基础,今天我们继续来看看双轴快排
是如何实现的。
在JDK 1.7中,
DualQuickSort
被首次引入,在1.8中做了一些优化,具体的变化我这里就不再对比了,有兴趣的同学可以去自己对比一下。接下来的分析基于JDK 1.8.1 u121的代码
双轴快排 - DualQuickSort
入口函数是
+Arrays.sort(int[] a)
,涉及到的函数有
~DualPivotQuicksort.sort(int[] a, int left, int right, int[] work, int workBase, int workLen)
-DualPivotQuicksort.sort(int[] a, int left, int right, boolean leftmost)
实际的执行函数有两个,我们依次来看。
双轴快排对外入口 - ~DualPivotQuicksort.sort(int[] a, int left, int right, int[] work, int workBase, int workLen)
我们先看一下它的调用流程
从图中可以看出,虽然本身的类名是双轴快排(DualPivotQuicksort
)。但是还会综合其它排序方法。比如上图中,如果检测出数组比较有序的情况下,则会使用merge sort
的思路来进行处理。
接下来,我们来看一下图中E
, J
是如何实现的。
E
我们先参考一下这部分的原始代码
/*
* Index run[i] is the start of i-th run
* (ascending or descending sequence).
*/
int[] run = new int[MAX_RUN_COUNT + 1]; // MAX_RUN_COUNT = 67
int count = 0; run[0] = left;
// Check if the array is nearly sorted
for (int k = left; k < right; run[count] = k) {
if (a[k] < a[k + 1]) {
// ascending
while (++k <= right && a[k - 1] <= a[k]);
} else if (a[k] > a[k + 1]) {
// descending
while (++k <= right && a[k - 1] >= a[k]);
for (int lo = run[count] - 1, hi = k; ++lo < --hi; ) {
int t = a[lo]; a[lo] = a[hi]; a[hi] = t;
}
} else {
// equal
for (int m = MAX_RUN_LENGTH; ++k <= right && a[k - 1] == a[k]; ) {
// MAX_RUN_LENGTH = 33
if (--m == 0) {
sort(a, left, right, true);
return;
}
}
}
/*
* The array is not highly structured,
* use Quicksort instead of merge sort.
*/
if (++count == MAX_RUN_COUNT) {
sort(a, left, right, true);
return;
}
}
// Check special cases
// Implementation note: variable "right" is increased by 1.
if (run[count] == right++) {
// The last run contains one element
run[++count] = right;
} else if (count == 1) {
// The array is already sorted
return;
}
在上述代码中,搞清楚变量run
, count
, k
, m
这4个变量的含义就比较容易理解代码的实现了。
我理解下来,它们分别的含义如下
run
- 记录每一个连续区间,对应的开始元素的下标count
- 记录到目前为止,已经检测到有多少个递增的区间,同时也是for循环的次数。注意其值从0开始,所以第1个区间的范围是[run[0], run[1])
,以此类推k
- 游标,整个for循环中,指向当前所遍历到的元素m
- 用于辅助判断一段连续相等的元素区间的的长度是否已经达到指定的上限
上面几个变量虽然用文字解释了一下,依然比较晦涩。这里我画一个简单的示意图,你就明白到底是如何检测是否有序,以及所谓有序的标准是什么了。
至于两个常量
MAX_RUN_COUNT
与MAX_RUN_LENGTH
的值是怎么来的,猜测依然是经验值吧。有清楚的同学可以告诉我。
for循环开始有if-elseif-else
的语句。是把当前元素与下一个元素做比较,将结果分成了3种情况
- 当前元素 < 下一个元素
- 当前元素 > 下一个元素
- 当前元素 = 下一个元素
其中<
这种情况最简单,已经是期望的升序区间。该block里边的while
循环则用于向右移动游标,直到找到第一个降序的元素。即
while (++k <= right && a[k - 1] <= a[k]);
>
这种情况相对复杂一些,这个block里边是找一个连续的降序区间。然后将该区间从两端到中间的方向首尾交换元素,从而将原本的降序区间转变成一个升序区间。即
while (++k <= right && a[k - 1] >= a[k]);
for (int lo = run[count] - 1, hi = k; ++lo <