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));