双轴快排
-
双轴快排是单轴快排的改进,初次学习双轴快排需要仔细深入地理解各处细节,因此本小节将详细介绍其实现细节,展示确定双轴位置既区间划分的过程。
-
前述快排每次递归确定当前区间的主轴,并利用该主轴将当前区间划分为左右两个部分。双轴快排则以 两个轴 (pivot1, pivot2)将当前区间划分为 三个子区间,双轴三区间的划分结果要满足如下。为方便叙述,将[left, pivot1)称作区间1,(pivot1, pivot2)称作区间2,(pivot2, right]称作区间3,其中pivot1,pivot2指的是最终位置,区间1,区间2,区间3均指划分后的最终区间。
arr[i] < arr[pivot1], i ∈ [left, pivot1) 区间1
arr[pivot1] ≤ arr[i] ≤ arr[pivot2], i ∈ (pivot1, pivot2) 区间2
arr[i] > arr[pivot2], i ∈ (pivot2, right] 区间3
对三个子区间执行同样的过程,直到无法划分时排序完成。算法主要过程和说明如下,结合后续代码实现的注释可准确把握各处细节。
dualPivotQuickSort执行开始,首先以if(left < right)为条件,只对大小大于等于2的区间执行双轴快排。
以如下语句令左右两端元素中较小者居左,后续以left为初始pivot1(下标),right为初始pivot2(下标),保证pivot1为左右两端元素中的较小者。
在程序后续内容中,arr[left]为pivot1的值(左轴值),arr[right]为pivot2的值(右轴值)。
if(arr[left] > arr[right]) {
swap(arr, left, right);
}
设置index = left + 1,lower = left + 1,upper = right - 1。
index表示当前考察的元素下标。
lower是用于推进到pivot1最终位置的动态向右扩展的下标(扩展区间1),在程序的任意时刻总有[left, lower)的元素确定在区间1中。
upper是用于推进到pivot2最终位置的动态向左扩展的下标(扩展区间3),在程序的任意时刻总有(upper, right]的元素确定在区间3中。
-
当循环结束时lower–和upper++为最终的pivot1和pivot2的位置。
-
初始时lower == left + 1,表示区间1元素个数为1,
因为lower以左(不含lower)才是确定在区间1中的元素。
在遍历结束后以两个swap完成双轴归位时,
最后一个确定在区间1的元素会与arr[left]交换。
所以说我们一开始就知道left处的元素最终一定在区间1中,
因此初始时令lower == left + 1。 -
upper == right - 1的初始取值也是基于同样的原因。
-
如果现在还无法很好地理解这一点,先将整个过程看完后再回过头来多推敲几次。
从此处开始,代码行为是要遍历从left + 1到right - 1的所有元素,通过与arr[left] (左轴值)和arr[right] (右轴值)的比较,以及元素交换操作,将 每一个元素正确地置于区间1,区间2和区间3中,与此同时,以lower的动态右移和upper的动态左移,不断扩展这三个区间。 通过index++,从左到右依次遍历所有元素,当所有元素遍历完成,也就意味着所有元素都已归于其应属的区间。显然,这些操作应在一个 循环 之内,下面进入该循环。
首先,循环的边界条件是while(index <= upper)。虽然还未开始分析upper的动态变化,但已经知道upper以右(不含upper)的元素是确定在区间3中的,index向右推进的时候不能超过upper,因为下标为upper + 1的元素是已确定在区间3中的 (但下标为upper的元素尚未确定其归属),所以是 <=。
以if(arr[index] < arr[left])考察arr[index]是否应在区间1,若满足则在区间1。这意味需要将该元素置于lower左侧,且区间1需向右扩展1位,通过如下两行,交换arr[index]和arr[lower]后lower++来完成。
swap(arr, index, lower);
lower++;
类似地,以else if(arr[index] > arr[right])考察arr[index]是否应在区间3,若满足则在区间3。这意味着需要将