用js實現快排

Eric_暱稱已被使用發表於2018-12-23
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 (a > b) {
    return 1;
  }
  return 0;
};

const quickSort = (
  unsortedArray,
  sortingAlgorithm = defaultSortingAlgorithm
) => {
  const sortedArray = TODO(unsortedArray);
  return sortedArray;
};
複製程式碼

由於我們是不斷地重複找基準點,然後輸出全小於基準點和全大於基準點的陣列的這個步驟。我們希望用遞迴來實現,這樣可以少寫程式碼。

你可以隨便找個基準點:第一個、中間、最後一個、隨機一個。為了簡單起見,我們假設基準點的選取對時間複雜度沒有影響。我在本文中總是使用最後一個元素作為基準點,因為要配合下圖的演示(圖中用的是最後一個元素,來源維基百科)。

用js實現快排

陣列基於基準點分成兩個陣列:小於基準點的陣列放前面,大於基準點的陣列放後面。最終,把基準點放在兩個陣列的中間,重複以上步驟。

為了不改變原資料,我們建立了新陣列。這不是必要的,但是是個好的習慣。

我們建立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本身現在是基準點,不再需要對其進行排序。

相關文章