Quick Sort 快速排序法

Quick Sort 快速排序法

複杂度

複杂度 Big O
最差时间複杂度 Worst-case time complexity Big O(n log n)
平均时间複杂度 Average-case time complexity Big theta (n log n)
最佳时间複杂度 Best-case time complexity Big omega (n log n)
空间複杂度 Space complexity Big O (n)

演算法

1. 取得数字阵列 pivot 分割中心点

确保 pivot 分割中心点 的左侧数字小于 pivot 分割中心点 的数字

确保 pivot 分割中心点 的右侧数字大于 pivot 分割中心点 的数字

2. 排序左右两侧数字阵列

pivot 分割中心点的数字及位置已确定

持续计算 pivot 分割中心点左侧及右侧阵列的子阵列

直到左侧与右侧已经递迴比较到最后无法再切割

Quick Sort 快速排序法

Quick Sort 快速排序法

QuickSelect 流程

Input: nums = [3,2,1,5,6,4], k = 2
Output: 5

A. 选择一用来比较的 pivotCheckNumber 当作检查数字

  • 确保「检查数字」左边的数字,都是比「检查数字」小
  • 确保「检查数字」右边的数字,都是比「检查数字」大

一开始随机选择 最右边 的数字当作检查数字

索引 0 1 2 3 4 5
数字 3 2 1 5 6 4
检查指标 左指标 右指标
其他指标 最小数字指标 检查数字指标

目标要将比「检查数字」 小的数字放在左边,比较大的就放在「检查数字」右边

所以就拿「最小数字指标」当作储存较小数字的指标位置,预设是最左边

B. 从左指标检查到右指标

「左指标」数字「检查数字」 小,就将「左指标」数字 放到 「最小数字指标」,而 「最小数字指标」 就往右移动,等待放入下一个比 「检查数字」 小的数字,然后持续检查到右指标

Step 1

  • 左指标数字 3检查数字指标数字 4 小,将 左指标数字 3最小数字指标 数值交换
  • 「最小数字指标」 往右移动
  • 左指标 往右移动
索引 0 1 2 3 4 5
数字 3 2 1 5 6 4
检查指标 左指标 右指标
其他指标 最小数字指标 检查数字指标

Step 2

  • 左指标数字 2检查数字指标数字 4 小,将 左指标数字 2最小数字指标 数值交换
  • 「最小数字指标」 往右移动
  • 左指标 往右移动
索引 0 1 2 3 4 5
数字 3 2 1 5 6 4
检查指标 左指标 右指标
其他指标 最小数字指标 检查数字指标

Step 3

  • 左指标数字 1检查数字指标数字 4 小,将 左指标数字 1最小数字指标 数值交换
  • 「最小数字指标」 往右移动
  • 左指标 往右移动
索引 0 1 2 3 4 5
数字 3 2 1 5 6 4
检查指标 左指标 右指标
其他指标 最小数字指标 检查数字指标

Step 4

  • 左指标数字 5检查数字指标数字 4 大,不做任何动作
  • 左指标 往右移动
索引 0 1 2 3 4 5
数字 3 2 1 5 6 4
检查指标 左指标 右指标
其他指标 最小数字指标 检查数字指标

Step 5

  • 左指标数字 6检查数字指标数字 4 大,不做任何动作
  • 左指标 往右移动
索引 0 1 2 3 4 5
数字 3 2 1 5 6 4
检查指标 左指标 & 右指标
其他指标 最小数字指标 检查数字指标

Step 6

  • 左指标 已经检查到 右指标 位置了,不再继续检查
  • 检查数字指标数字 4最小数字指标数字 5 交换
  • 所以目前切割的位置在 最小数字指标索引 3 的位置,数值是 4
    • 最小数字指标索引 3 的位置右边都是比 4 大的数字
    • 最小数字指标索引 3 的位置左边都是比 4 小的数字
  • 所以目前 最小数字指标索引 的切割位置及数字都是确定的
索引 0 1 2 3 4 5
数字 3 2 1 4 6 5
检查指标 左指标 & 右指标
其他指标 最小数字指标 检查数字指标

Step 7

因为要选择第 k 大的数字,所以在排序的数字阵列中,选择 (length - k) 阵列索引 的数字

  • 如果要选第 1 大的数字,等于是要选择索引阵列中 (6 - 1) = 5 第 5 个索引 位置的数字
  • 如果要选第 2 大的数字,等于是要选择索引阵列中 (6 - 2) = 4 第 4 个索引 位置的数字
  • 如果要选第 3 大的数字,等于是要选择索引阵列中 (6 - 3) = 3 第 3 个索引 位置的数字
  • 如果要选第 4 大的数字,等于是要选择索引阵列中 (6 - 4) = 2 第 2 个索引 位置的数字
  • …以此类推

状况 1: 要找的数字是刚好是切割位置

确定的 切割位置最小数字指标,所以索引位置确定会是 3,数字确定会是 4

若刚好要找 第 3 大的数字,等于是要选择索引阵列中 (6 - 3) = 3 第 3 个索引 位置的数字

等于 目前的切割点 就是要找的 第 k 大数字,可以直接回传 4

索引 0 1 2 3 4 5
数字 3 2 1 4 6 5
检查指标 左指标 & 右指标
其他指标 最小数字指标 检查数字指标

状况 2: 要找的数字是大小是大于切割位置的数字

假如要找 第 2 大 的数字,等于是要选择索引阵列中 (6 - 2) = 4 第 4 个索引 位置的数字

但目前的切割位置是在 最小数字指标,第 3 个索引,而刚刚切割流程中,已经确定

  • 最小数字指标索引 3 的位置右边都是比 4 大的数字
  • 最小数字指标索引 3 的位置左边都是比 4 小的数字

表示现在要往 切割位置右方 去找,才能找到 第 2 大 的数字,所以就重複刚刚的步骤,重新对右半边的数字阵列做切割比较即可

  • 可以将 左指标 设定在 切割位置 右边 4
  • 右边区块的最小数字指标设定与 左指标 相同
  • 然后重複刚刚的步骤,直到找到 切割位置索引位置第 k 大数字 相同,符合 状况 1: 要找的数字是刚好是切割位置 即可
索引 0 1 2 3 4 5
数字 3 2 1 4 6 5
检查指标 左指标 右指标
其他指标 X X X X 最小数字指标 检查数字指标

状况 3: 要找的数字是大小是小于切割位置的数字

假如要找 第 4 大 的数字,等于是要选择索引阵列中 (6 - 4) = 2 第 2 个索引 位置的数字

状况 2 雷同,只是要反过来往 切割位置左方 去找,才能找到 第 4 大 的数字,所以就重複刚刚的步骤,重新对左半边的数字阵列做切割比较即可

  • 可以将 右指标 设定在 切割位置 左边 2
  • 左边区块的最小数字指标设定与 左指标 相同
  • 然后重複刚刚的步骤,直到找到 切割位置索引位置第 k 大数字 相同,符合 状况 1: 要找的数字是刚好是切割位置 即可
索引 0 1 2 3 4 5
数字 3 2 1 4 6 5
检查指标 左指标 右指标
其他指标 最小数字指标 检查数字指标 X X X

程式: 使用左侧第一个数字当作 pivot 数字

JavaScript

function partition(numberList, leftPointer, rightPointer) {
    var _a, _b;
    if (leftPointer === void 0) { leftPointer = 0; }
    if (rightPointer === void 0) { rightPointer = numberList.length - 1; }
    // 选择第一个元素是 pivot 数字
    var pivotNumber = numberList[leftPointer];
    // 交换资料索引起始位置
    var swapPointer = leftPointer;
    // 与 pivot 数字比较,将较小的数字放左侧,较大的数字放右侧
    for (var i = leftPointer + 1; i <= rightPointer; i++) {
        // 从第 2 个数字开始检查
        var currentCompareNumber = numberList[i];
        if (currentCompareNumber < pivotNumber) {
            // 「目前的数字」比「pivot 数字」还要小,要放在「pivot 数字」左方
            // 将「目前的数字」移动到「交换索引」位置
            swapPointer++;
            _a = [numberList[swapPointer], numberList[i]], numberList[i] = _a[0], numberList[swapPointer] = _a[1];
        }
    }
    // 将第 1 个 pivot 数字放到中间,这样可以让左侧数字是小于 pivot 数字,右侧数字大于 pivot 数字
    _b = [numberList[leftPointer], numberList[swapPointer]], numberList[swapPointer] = _b[0], numberList[leftPointer] = _b[1];
    // 回传目前 Pivot 的中心点在哪
    return swapPointer;
}
function quickSort(sortNumberList, leftPointer, rightPointer) {
    if (leftPointer === void 0) { leftPointer = 0; }
    if (rightPointer === void 0) { rightPointer = sortNumberList.length - 1; }
    // Base case is that the left and right pointers don't overlap, after which we'll be left with an array of 1 item
    if (leftPointer < rightPointer) {
        // 如果「左指标」小于「右指标」,表示还没比较完,可以继续比较
        var pivotIndex = partition(sortNumberList, leftPointer, rightPointer);
        // 对中心点左侧重新做 quick sort 排序,直到左侧所有元素都按照顺序排序
        quickSort(sortNumberList, leftPointer, pivotIndex - 1);
        // 对中心点右侧重新做 quick sort 排序,直到右侧所有元素都按照顺序排序
        quickSort(sortNumberList, pivotIndex + 1, rightPointer);
    }
    return sortNumberList;
}
var items = [5, 3, 7, 6, 2, 9, -1, 11];
console.log(quickSort(items, 0, items.length - 1));

TypeScript

function partition(numberList: number[], leftPointer: number = 0, rightPointer: number = numberList.length - 1) {
    // 选择第一个元素是 pivot 数字
    let pivotNumber = numberList[leftPointer];
    // 交换资料索引起始位置
    let swapPointer = leftPointer;

    // 与 pivot 数字比较,将较小的数字放左侧,较大的数字放右侧
    for (let i = leftPointer + 1; i <= rightPointer; i++) {
        // 从第 2 个数字开始检查
        let currentCompareNumber = numberList[i];
        if (currentCompareNumber < pivotNumber) {
            // 「目前的数字」比「pivot 数字」还要小,要放在「pivot 数字」左方
            // 将「目前的数字」移动到「交换索引」位置
            swapPointer++;
            [numberList[i], numberList[swapPointer]] = [numberList[swapPointer], numberList[i]];
        }
    }

    // 将第 1 个 pivot 数字放到中间,这样可以让左侧数字是小于 pivot 数字,右侧数字大于 pivot 数字
    [numberList[swapPointer], numberList[leftPointer]] = [numberList[leftPointer], numberList[swapPointer]];

    // 回传目前 Pivot 的中心点在哪
    return swapPointer;
}

function quickSort(sortNumberList: number[], leftPointer: number = 0, rightPointer: number = sortNumberList.length - 1) {
    // Base case is that the left and right pointers don't overlap, after which we'll be left with an array of 1 item
    if (leftPointer < rightPointer) {
        // 如果「左指标」小于「右指标」,表示还没比较完,可以继续比较
        let pivotIndex = partition(sortNumberList, leftPointer, rightPointer);

        // 对中心点左侧重新做 quick sort 排序,直到左侧所有元素都按照顺序排序
        quickSort(sortNumberList, leftPointer, pivotIndex - 1);

        // 对中心点右侧重新做 quick sort 排序,直到右侧所有元素都按照顺序排序
        quickSort(sortNumberList, pivotIndex + 1, rightPointer);
    }

    return sortNumberList;
}

let items = [5, 3, 7, 6, 2, 9, -1, 11];

console.log(quickSort(items, 0, items.length - 1));

程式: 使用右侧第一个数字当作 pivot 数字

TypeScript

// 切割数字阵列
var partitionNumberList = function(numsList:number[], leftPointer: number, rightPointer:number) {
    // 选择右指标当作「检查数字」
    let pivotCheckNumber = numsList[rightPointer];
    // 较小数字纪录指标设定为左指标
    let smallerNumberPointer = leftPointer;

    for(let i = leftPointer; i < rightPointer; i++) {
        // 从「左指标」检查到「右指标」
        if (numsList[i] <= pivotCheckNumber) {
            // 若「目前检查的数字」小于等于「检查的数字」
            // 将目前数字放到「较小数字记录指标」位置
            [numsList[smallerNumberPointer], numsList[i]] = [numsList[i], numsList[smallerNumberPointer]];
            // 「较小数字记录指标」位置往后移动
            smallerNumberPointer++;
        }
    }
    // 检查完成,确认「较小数字记录指标」数字都比「检查数字」小
    // 「较小数字记录指标」与「检查数字」交换
    //  - 「较小数字记录指标」左方都是比「检查数字」小的数字
    //  - 「较小数字记录指标」右方都是比「检查数字」大的数字
    [numsList[smallerNumberPointer], numsList[rightPointer]] = [numsList[rightPointer], numsList[smallerNumberPointer]];

    return smallerNumberPointer;
};


function quickSort(sortNumberList: number[], leftPointer: number = 0, rightPointer: number = sortNumberList.length - 1) {
    // Base case is that the left and right pointers don't overlap, after which we'll be left with an array of 1 item
    if (leftPointer < rightPointer) {
        // 如果「左指标」小于「右指标」,表示还没比较完,可以继续比较
        // 取得数字阵列 pivot 分割中心点
        let pivotIndex = partitionNumberList(sortNumberList, leftPointer, rightPointer);

        // 对中心点左侧重新做 quick sort 排序,直到左侧所有元素都按照顺序排序
        quickSort(sortNumberList, leftPointer, pivotIndex - 1);

        // 对中心点右侧重新做 quick sort 排序,直到右侧所有元素都按照顺序排序
        quickSort(sortNumberList, pivotIndex + 1, rightPointer);
    }

    return sortNumberList;
}

let items = [5, 3, 7, 6, 2, 9, -1, 11];

console.log(quickSort(items, 0, items.length - 1));

参考资料