双轴快速排序

35 阅读3分钟

双轴快速排序

算法概述

双轴快速排序算法,也叫双轴快速排序算法,是快速排序算法的一个改进版本。该排序算法选取两个基准值,在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];
}