Skip to main content

Quick Select

Quick Select 簡而言之就是使用基準值 (pivot) 比對排序,並透過 Recursion 的技巧,不斷將每個元素放到正確的位置上。 (之前的 Quick Sort 就是使用此方法來實作,只是忘了 Quick Select 直接略過先寫 Quick Sort 了XD)

Fundamental​

主要概念在 Quick Sort 都有詳細解釋,此演算法適合用來在陣列中找到第 kth 的數字,它不用像 Quick Sort 兩邊都要處理排序,只需要在第 kth 的數字在的那一邊處理比對即可,因此可以將其簡化成每次算出的基準值 (pivot) 若比 kth 大,就往左半邊找,若比 kth 小,就往右半邊找,若相等,就回傳答案。

Implementation​

function quickSelect(nums, start, end, kth) {
if (start === end) return nums[start];

const pivot = pivotHelper(nums, start, end);

if (pivot === kth) {
return nums[pivot];
} else if (pivot > kth) {
return quickSelect(nums, start, pivot - 1, kth);
} else {
return quickSelect(nums, pivot + 1, end, kth);
}
}

pivotHelper 的部分就直接複製貼過來了:

function pivotHelper(array, startIndex, endIndex) {
const swap = (arr, index1, index2) => {
[arr[index1], arr[index2]] = [arr[index2], arr[index1]];
};

let pivot = array[startIndex];
let pivotIndex = startIndex;

for (let i = startIndex + 1; i <= endIndex; i++) {
if (array[i] < pivot) {
pivotIndex++;
swap(array, pivotIndex, i);
}
}

swap(array, startIndex, pivotIndex);
return pivotIndex;
}

Big O Complexity​

Time Complexity (Best)Time Complexity (Average)Time Complexity (Worst)Space ComplexitySpace Complexity (Worst)
O(n)O(n)O(n²)O(log n)O(n)

最差的情況下和 Quick Sort 一樣,每輪排序完後,基準值的其中一邊只有 1 或 0 個元素,根本沒有剖半到。所以時間複雜度是 O(n²),同理空間複雜度也會因此變成每個元素都要 Recursion 一次 所以是 O(n) ,但是這個情況可以用 隨機取基準值 的方式來避開。

至於平均和最佳情況的時間雜度是 O(n) 的原因是每次剖半後只比對其中一邊: n + 1/2 * n + 1/4 * n + 1/8 * n + 1/16 * n ...,會無限趨近於 2n 。

Optimization​

上段提到避開最差的情況可以用 隨機取基準值 的方式,以下就來修改下 pivotHelper 的實作:

function pivotHelper(array, startIndex, endIndex) {
const swap = (arr, index1, index2) => {
[arr[index1], arr[index2]] = [arr[index2], arr[index1]];
};

let pivotIndex = left + Math.floor(Math.random() * (right - left)); // 取隨機一個位置當作 pivot
let pivot = array[pivotIndex];

swap(array, startIndex, pivotIndex); // 將基準值放在第一個位置

// 因為第一個位置是基準值,所以從第二個開始比較
for (let i = startIndex + 1; i <= endIndex; i++) {
if (array[i] < pivot) {
pivotIndex++;
swap(array, pivotIndex, i);
}
}

swap(array, startIndex, pivotIndex); // 一樣,比對完後將基準值所在的第一個位置和最終比對出來的位置調換。
return pivotIndex;
}

雖然還是有機率隨機取基準值後都是取到最左或最右的位置,所以最差情況依然是 O(n²),但是在實務上可以很大幅度避開且接近平均情況。