Quick Sort 快速排序法
Categories:
複杂度
複杂度 | 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 分割中心点左侧及右侧阵列的子阵列
直到左侧与右侧已经递迴比较到最后无法再切割
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));