By Charles Stover | Dec 12, 2018
原理?
Quicksort通過從陣列中選取一個元素並將其表示為基準點,把陣列中的所有其他元素分為兩類 – 它們小於或大於此基準點。
然後把作為這一輪排序結果的兩個陣列(陣列元素都小於基準點的陣列和陣列元素都大於基準點的陣列)再進行相同的排序。即分別再選個基準點,然後基於基準點分成兩個陣列元素分別小於和大於基準點的陣列。
最終,由於最後陣列中沒有元素或只有一個元素,因此不用再比較了。剩下的值都已經基於基準點排好序了。
(譯者:內容有刪減,說的有些囉嗦)
如何實現
js的Array原型的sort方法使用另外一種方法實現排序的,我們不用這個實現快排。我們自己建立一個方法,待排序的陣列作為引數,輸出排好序的陣列。
const quickSort = (unsortedArray) =>
{
const sortedArray = TODO(unsortedArray);
return sortedArray;
};
複製程式碼
由於陣列中項的“值”可能不是很明顯,我們應該為排序演算法提供一個可選引數。在js中,字串和數字會排好序的,但是物件不會。我們要根據user物件的age欄位給陣列排序。
const defaultSortingAlgorithm = (a, b) =>
{
if (a <
b) {
return -1;
} if (b >
a) {
return 1;
} return 0;
};
const quickSort = ( unsortedArray, sortingAlgorithm = defaultSortingAlgorithm) =>
{
const sortedArray = TODO(unsortedArray);
return sortedArray;
};
複製程式碼
由於我們是不斷地重複找基準點,然後輸出全小於基準點和全大於基準點的陣列的這個步驟。我們希望用遞迴來實現,這樣可以少寫程式碼。
你可以隨便找個基準點:第一個、中間、最後一個、隨機一個。為了簡單起見,我們假設基準點的選取對時間複雜度沒有影響。我在本文中總是使用最後一個元素作為基準點,因為要配合下圖的演示(圖中用的是最後一個元素,來源維基百科)。
陣列基於基準點分成兩個陣列:小於基準點的陣列放前面,大於基準點的陣列放後面。最終,把基準點放在兩個陣列的中間,重複以上步驟。
為了不改變原資料,我們建立了新陣列。這不是必要的,但是是個好的習慣。
我們建立recursiveSort作為遞迴函式,它將遞迴子陣列(從起始索引到結束索引),中途改變sortedArray陣列的資料。整個陣列是第一個傳遞給此遞迴函式的陣列。
最後,返回排好序的陣列。
recursiveSort函式有一個pivotValue變數來表示我們的基準點,還有一個splitIndex變數來表示分隔小於和大於陣列的索引。從概念上講,所有小於基準點的值都將小於splitIndex,而所有大於基準點的值都將大於splitIndex。splitIndex被初始化為子陣列的開頭,但是當我們發現小於基準點時,我們將相應地調整splitIndex。
我們將迴圈遍歷所有值,將小於基準點的值移動到起始索引之前。
const quickSort = ( unsortedArray, sortingAlgorithm = defaultSortingAlgorithm) =>
{
// Create a sortable array to return. const sortedArray = [ ...unsortedArray ];
// Recursively sort sub-arrays. const recursiveSort = (start, end) =>
{
// If this sub-array contains less than 2 elements, it's sorted. if (end - start <
1) {
/*譯者:經熱心觀眾提醒,這裡應該是小於1,而不是小於2*/ return;
} const pivotValue = sortedArray[end];
let splitIndex = start;
for (let i = start;
i <
end;
i++) {
const sort = sortingAlgorithm(sortedArray[i], pivotValue);
// This value is less than the pivot value. if (sort === -1) {
// If the element just to the right of the split index, // isn't this element, swap them. if (splitIndex !== i) {
const temp = sortedArray[splitIndex];
sortedArray[splitIndex] = sortedArray[i];
sortedArray[i] = temp;
} // Move the split index to the right by one, // denoting an increase in the less-than sub-array size. splitIndex++;
} // Leave values that are greater than or equal to // the pivot value where they are.
} // Move the pivot value to between the split. sortedArray[end] = sortedArray[splitIndex];
sortedArray[splitIndex] = pivotValue;
// Recursively sort the less-than and greater-than arrays. recursiveSort(start, splitIndex - 1);
recursiveSort(splitIndex + 1, end);
};
// Sort the entire array. recursiveSort(0, unsortedArray.length - 1);
return sortedArray;
};
複製程式碼
我們將所有小於基準點的值移動到splitIndex指向的位置,其他的值不動(預設情況下,大於splitIndex,因為splitIndex從子陣列的開頭開始)。
一旦子陣列被排序好後,我們將基準點放在中間,因為排序就是基於基準點排的,我們知道它的位置。
左邊的所有值(從start到splitIndex – 1)都會被遞迴排序,並且右邊的所有值(從splitIndex + 1到end)也都會被遞迴排序。 splitIndex本身現在是基準點,不再需要對其進行排序。