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