双轴快速排序
算法概述
双轴快速排序算法,也叫双轴快速排序算法,是快速排序算法的一个改进版本。该排序算法选取两个基准值,在partition的时候将待排序区间分成3个区间。
Javascript实现
要点一,获取基准值
- 在常规快速排序中,可以取第一个元素作为基准值;
- 一般采用随机取值法、三点取中法等优化方法来获取基准值
- 笔者直接用首尾值作为两个基准值,有兴趣的读者可以思考一下有没有更优的方法(原则就是用常数级别的时空复杂度取到两个基准值)
- 获取两个基准值 p1 <= p2
要点二,对数组进行分区
- 划分原则:p1,p2要落在两个分区中,如果p1 === p2,则将p1和p2放在中间分区
- 笔者用两个基准值将【l,r】分割成三个区间【l,lt】:< q1 ,【lt + 1,gt - 1】:q1 <= x < q2,【gt,r】: >= q2
- 笔者使用闭区间[]定义上述三个区间;也可以使用半开半闭区间[)、(]和开区间()定义上述三个区间
- 区间定义方式不同,初始化时的 lt,gt以及代码中的判断条件也有区别,有兴趣的读者可以自行尝试用另外两种方式定义上述三个区间
const arr = [9,7,7,7,9];
const sortedArr = clone(arr);
console.log("排序前:", arr);
quickSort(sortedArr,0, sortedArr.length - 1);
console.log("排序后:", sortedArr);
function quickSort(arr, l, r) {
if (l >= r) return;
console.log("待排序区间:", l, r);
// 区间只有两个数,没必要partition,直接排序返回
if (r - l + 1 <= 2) {
if(arr[l] > arr[r]) swap(arr, l, r);
return;
}
// 要点一,获取基准值
// 在常规快速排序中,可以取第一个元素作为基准值;
// 一般采用随机取值法、三点取中法等优化方法来获取基准值
// 笔者直接用首尾值作为两个基准值,有兴趣的读者可以思考一下有没有更优的方法(原则就是用常数级别的时空复杂度取到两个基准值)
// 获取两个基准值 p1 <= p2
let [p1, p2] = getPivot(arr, l, r);
console.log("两个基准值:", p1, p2);
// 要点二,对数组进行分区
// 划分原则:p1,p2要落在两个分区中
// 用两个基准值将【l,r】分割成三个区间【l,lt】:< q1 ,【lt + 1,gt - 1】:q1 <= x < q2,【gt,r】: >= q2
// 笔者使用闭区间[]定义上述三个区间;也可以使用半开半闭区间[)、(]和开区间()定义上述三个区间
// 区间定义方式不同,初始化时的 lt,gt以及代码中的判断条件也有区别,有兴趣的读者可以自行尝试用另外两种方式定义上述三个区间
let [lt, gt] = partition(arr, l, r, p1, p2);
console.log("三个区间:", l, lt, gt, r);
console.log("区间划分:", arr.slice(l, lt + 1), arr.slice(lt + 1, gt), arr.slice(gt, r + 1));
let s1 = lt - l + 1,s2 = gt - 1 - lt,s3 = r - gt + 1;
// 递归调用
if(s1) quickSort(arr, l, lt);
if(s2 && p1 !== p2) quickSort(arr, lt + 1, gt - 1);
if(s3) quickSort(arr, gt, r);
}
// -------------辅助函数--------------
function swap(arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
function clone(arr) {
return arr.slice(0);
}
function getPivot(arr, l, r) {
let p1 = arr[l], p2 = arr[r];
if (p1 < p2) return [p1, p2];
return [p2, p1];
}
function partition(arr, l, r, p1, p2) {
let lt = l - 1, gt = r + 1; // 闭区间时的初始化值
for (let i = l; i < gt; i++) {
let c = arr[i];
if (c < p1) {
lt++;
swap(arr, i, lt);
} else if (c >= p2 && p1 !== p2 || c > p2 && p1 === p2) { // 当p1 === p2时,p1,p2落在中间区间,方便处理
gt--;
swap(arr, i, gt);
i--; // 读者们思考下为什么要i--
}
}
return [lt, gt];
}