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 分割中心點左側及右側陣列的子陣列
直到左側與右側已經遞迴比較到最後無法再切割


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