優雅的 JavaScript 排序算法(ES6)

面試官:小夥子排序算法瞭解嗎?javascript

回答:我能寫出來四種冒泡排序兩種選擇排序兩種插入排序兩種哈希排序兩種歸併排序兩種堆排序四種快速排序html

用我本身的方式。前端

前言

文中全部代碼位於位於此代碼倉庫中,推薦下載代碼進行練習、推敲。java


(已過時)號外:博主爲 18 屆應屆生,目前狀態是前端開發補招進行時。若有內推機會,歡迎一波流帶走 :》git

更多信息,歡迎check: rayjune.me/aboutes6

另,若是以爲這些用心推敲的代碼對你有幫助的話,歡迎 star 一下代碼倉庫衆籌博主找到一份體面的工做,在這裏給你們遞茶了:)github


P.S. 原文顯示效果更好喔:) check:rayjune.me/優雅的 JavaScr…面試

做者:RayJune轉載請署名,請尊重博主含辛茹苦、遍查資料、一行一行含淚碼出來的成果。參考&感謝 部分裏代碼參考地址都已列出)算法

另,本文中常使用 swap 函數,在這裏提早列出來,如下就省略了。shell

function swap(arr, indexA, indexB) {
  [arr[indexA], arr[indexB]] = [arr[indexB], arr[indexA]];
}
複製代碼

冒泡排序 Bubble Sort

簡明解釋

經過依次比較、交換相鄰的元素大小(按照由小到大的順序,若是符合這個順序就不用交換)。

1 次這樣的循環能夠獲得一個最大值,n - 1 次這樣的循環能夠排序完畢

屬性

  • 穩定
  • 時間複雜度 O(n²)
  • 交換 O(n²)
  • 對即將排序完成的數組進行排序 O(n)(可是這種狀況下不如插入排序塊,請繼續看下文)

核心概念

  • 利用交換,將最大的數冒泡到最後
  • 使用緩存 postion 來優化
  • 使用雙向遍歷來優化

初版:基本實現

function bubbleSort(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    for (let j = 0; j < i; j++) {
      if (arr[j] > arr[j + 1]) {
        swap(arr, j, j + 1);
      }
    }
  }

  return arr;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(bubbleSort(arr));
複製代碼

第二版:緩存 pos

設置一標誌性變量 pos,用於記錄每趟排序中最後一次進行交換的位置。 因爲 pos 位置以後的記錄均已交換到位,故在進行下一趟排序時只要掃描到 pos 位置便可

function bubbleSort2(arr) {
  let i = arr.length - 1;

  while (i > 0) {
    let pos = 0;

    for (let j = 0; j < i; j++) {
      if (arr[j] > arr[j + 1]) {
        pos = j;
        swap(arr, j, j + 1);
      }
    }
    i = pos;
  }

  return arr;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(bubbleSort2(arr));
複製代碼

第三版:雙向遍歷

傳統冒泡排序中每一趟排序操做只能找到一個最大值或最小值, 咱們能夠 在每趟排序中進行正向和反向兩遍冒泡 , 一次能夠獲得兩個最終值(最大和最小) , 從而使外排序趟數幾乎減小了一半

function bubbleSort3(arr) {
  let start = 0;
  let end = arr.length - 1;

  while (start < end) {
    for (let i = start; i < end; i++) {
      if (arr[i] > arr[i + 1]) {
        swap(arr, i, i + 1);
      }
    }
    end -= 1;
    for (let i = end; i > start; i--) {
      if (arr[i - 1] > arr[i]) {
        swap(arr, i - 1, i);
      }
    }
    start += 1;
  }

  return arr;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(bubbleSort3(arr));
複製代碼

第四版:結合 2&3

前兩種優化方式(緩存 pos、雙向遍歷)的結合:

function bubbleSort4(arr) {
  let start = 0;
  let end = arr.length - 1;

  while (start < end) {
    let endPos = 0;
    let startPos = 0;

    for (let i = start; i < end; i++) {
      if (arr[i] > arr[i + 1]) {
        endPos = i;
        swap(arr, i, i + 1);
      }
    }
    end = endPos;
    for (let i = end; i > start; i--) {
      if (arr[i - 1] > arr[i]) {
        startPos = i;
        swap(arr, i - 1, i);
      }
    }
    start = startPos;
  }

  return arr;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(bubbleSort4(arr));
複製代碼

螞蟻金服面試

來自於螞蟻金服的一道面試題:

對於冒泡排序來講,能不能傳入第二個參數(參數爲函數),來控制升序和降序?(聯想一下 array.sort()

function bubbleSort(arr, compareFunc) {
  for (let i = arr.length - 1; i > 0; i--) {
    for (let j = 0; j < i; j++) {
      if (compareFunc(arr[j], arr[j + 1]) > 0) {
        swap(arr, j, j + 1);
      }
    }
  }

  return arr;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(bubbleSort(arr, (a, b) => a - b));
console.log(bubbleSort(arr, (a, b) => b - a));
複製代碼

選擇排序 Selection Sort

簡明解釋

每一次內循環遍歷尋找最小的數,記錄下 minIndex,並在此次內循環結束後交換 minIndexi 的位置

重複這樣的循環 n - 1 次即獲得結果。

屬性

  • 不穩定
  • Θ(n²) 不管什麼輸入,均爲 Θ(n²)
  • Θ(n) 交換: 注意,這裏只有 n 次的交換,選擇排序的惟一優勢*

關於 Θ(n) swaps:

Selection sort has the property of minimizing the number of swaps. In applications where the cost of swapping items is high, selection sort very well may be the algorithm of choice.

可見即便是咱們以爲最慢的選擇排序,也有它的用武之地

核心概念

  • 「可預測」的時間複雜度,什麼進來都是 O(n²),但不穩定,惟一的優勢是減小了 swap 次數

初版:基本實現

function selectionSort(arr) {
  for (let i = 0, len = arr.length; i < len - 1; i++) {
    let minIndex = i;

    for (let j = i + 1; j < len; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j;
      }
    }
    if (i !== minIndex) {
      swap(arr, i, minIndex);
    }
  }

  return arr;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(selectionSort(arr));
複製代碼

第二版:找到最大值

若是你想在每次內循環中找到最大值並把其交換到數組的末尾(相比較 minIndex 有點麻煩),如下是實現的代碼:

function selectionSort2(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    let maxIndex = i;

    for (let j = i - 1; j >= 0; j--) {
      if (arr[j] > arr[maxIndex]) {
        maxIndex = j;
      }
    }
    if (i !== maxIndex) {
      swap(arr, i, maxIndex);
    }
  }

  return arr;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(selectionSort2(arr));
複製代碼

插入排序 Insertion Sort

簡明解釋

默認 a[0] 爲已排序數組中的元素arr[1] 開始逐漸往已排序數組中插入元素從後往前一個個比較,若是待插入元素小於已排序元素,則已排序元素日後移動一位,直到待插入元素找到合適的位置並插入已排序數組。

通過 n - 1 次這樣的循環插入後排序完畢。

屬性

  • 穩定
  • 適合場景:對快要排序完成的數組時間複雜度爲 O(n)
  • 很是低的開銷
  • 時間複雜度 O(n²)

因爲它的優勢(自適應,低開銷,穩定,幾乎排序時的O(n)時間),插入排序一般用做遞歸基本狀況(當問題規模較小時)針對較高開銷分而治之排序算法, 如希爾排序快速排序

核心概念

  • 高性能(特別是接近排序完畢時的數組),低開銷,且穩定
  • 利用二分查找來優化

初版:基本實現

function insertionSort(arr) {
  for (let i = 1, len = arr.length; i < len; i++) {
    const temp = arr[i];
    let preIndex = i - 1;

    while (arr[preIndex] > temp) {
      arr[preIndex + 1] = arr[preIndex];
      preIndex -= 1;
    }
    arr[preIndex + 1] = temp;
  }

  return arr;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(insertionSort(arr));
複製代碼

二分查找算法

由於對於插入排序的優化方法是二分查找優化,這裏補充一下二分查找的算法的實現。

核心概念是:折半

function binarySearch(arr, value) {
  let min = 0;
  let max = arr.length - 1;
  
  while (min <= max) {
    const mid = Math.floor((min + max) / 2);

    if (arr[mid] === value) {
      return mid;
    } else if (arr[mid] > value) {
      max = mid - 1;
    } else {
      min = mid + 1;
    }
  }

  return 'Not Found';
}

// test
const arr = [1, 2, 3];
console.log(binarySearch(arr, 2));  // 1
console.log(binarySearch(arr, 4));  // Not Found
複製代碼

第二版:使用二分查找

首先把二分查找算法作一點小修改,以適應咱們的插入排序:

function binarySearch(arr, maxIndex, value) {
  let min = 0;
  let max = maxIndex;
  
  while (min <= max) {
    const mid = Math.floor((min + max) / 2);

    if (arr[mid] <= value) {
      min = mid + 1;
    } else {
      max = mid - 1;
    }
  }

  return min;
}
複製代碼

而後在查找插入位置時使用二分查找的方式來優化性能:

function insertionSort2(arr) {
  for (let i = 1, len = arr.length; i < len; i++) {
    const temp = arr[i];
    const insertIndex = binarySearch(arr, i - 1, arr[i]);

    for (let preIndex = i - 1; preIndex >= insertIndex; preIndex--) {
      arr[preIndex + 1] = arr[preIndex];
    }
    arr[insertIndex] = temp;
  }

  return arr;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(insertionSort2(arr));
複製代碼

希爾排序 Shell Sort

簡明解釋

希爾排序是插入排序的改進版,它克服了插入排序只能移動一個相鄰位置的缺陷(希爾排序能夠一次移動 gap 個距離),利用了插入排序在排序幾乎已經排序好的數組的很是快的優勢

使用能夠動態定義的 gap 來漸進式排序,先排序距離較遠的元素,再逐漸遞進,而實際上排序中元素最終位置距離初始位置遠的機率是很大的,因此希爾排序大大提高了性能(尤爲是 reverse 的時候很是快,想象一下這時候冒泡排序和插入排序的速度)。

並且希爾排序不只效率較高(比冒泡和插入高),它的代碼相對要簡短,低開銷(繼承插入排序的優勢),追求這些特色(效率要求過得去就好,代碼簡短,開銷低,且數據量較小)的時候希爾排序是好的 O(n·log(n)) 算法的替代品

總而言之:希爾排序的性能優化來自增量隊列的輸入gap 的設定

屬性

  • 不穩定
  • 在快要排序完成的數組有 O(n·log(n)) 的時間複雜度(而且它對於反轉數組的速度很是快)
  • O(n^3/2) time as shown (想要了解更多細節,請查閱 wikipedia Shellsort

關於不穩定:

咱們知道, 單次直接插入排序是穩定的,它不會改變相同元素之間的相對順序,但在屢次不一樣的插入排序過程當中, 相同的元素可能在各自的插入排序中移動,可能致使相同元素相對順序發生變化。所以, 希爾排序並不穩定

關於 worse-case time 有一點複雜:

The worse-case time complexity of shell sort depends on the increment sequence. For the increments 1 4 13 40 121…, which is what is used here, the time complexity is O(n3/2). For other increments, time complexity is known to be O(n4/3) and even O(n·log2(n)).

核心概念

希爾排序是基於插入排序的如下兩點性質而提出改進方法的:

  1. 插入排序在對幾乎已經排好序的數據操做時,效率高,便可以達到 O(n) 的效率
  2. 但插入排序通常來講是低效的,由於插入排序每次只能將數據移動一位

其中 gap(增量)的選擇是希爾排序的重要部分。只要最終 gap 爲 1 任何 gap 序列均可以工做。算法最開始以必定的 gap 進行排序。而後會繼續以必定 gap 進行排序,直到 gap = 1 時,算法變爲插入排序

Donald Shell 最初建議 gap 選擇爲 n / 2 而且對 gap 取半直到 gap 達到 1 。雖然這樣取能夠比 O(n²) 類的算法(插入排序、冒泡排序)更好,但這樣仍然有減小平均時間和最差時間的餘地。 (關於優化 gap 的細節涉及到複雜的數學知識,咱們這裏不作深究,詳細能夠參考 wikipedia 上的頁面

初版:基本實現

Donald Shell 的最初建議(gap = n / 2)版代碼(方便理解):

function shellSort(arr) {
  const len = arr.length;
  let gap = Math.floor(len / 2);

  while (gap > 0) {
    // 注意下面這段 for 循環和插入排序極爲類似
    for (let i = gap; i < len; i++) {
      const temp = arr[i];
      let preIndex = i - gap;

      while (arr[preIndex] > temp) {
        arr[preIndex + gap] = arr[preIndex];
        preIndex -= gap;
      }
      arr[preIndex + gap] = temp;
    }
    gap = Math.floor(gap / 2);
  }

  return arr;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(shellSort(arr));
複製代碼

第二版:Knuth's increment sequence

常見的、易生成的、優化 gap 的序列方法(來自 Algorithms (4th Edition) ,有些更快的方法但序列不容易生成,由於用到了比較深奧的數學公式):

function shellSort(arr) {
  const len = arr.length;
  let gap = 1;

  while (gap < len / 3) {
    gap = gap * 3 + 1;
  }
  while (gap > 0) {
    for (let i = gap; i < len; i++) {
      const temp = arr[i];
      let preIndex = i - gap;

      while (arr[preIndex] > temp) {
        arr[preIndex + gap] = arr[preIndex];
        preIndex -= gap;
      }
      arr[preIndex + gap] = temp;
    }
    gap = Math.floor(gap / 2);
  }

  return arr;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(shellSort(arr));
複製代碼

歸併排序 Merge Sort

簡明解釋

歸併排序使用分而治之的思想,以折半的方式來遞歸/迭代排序元素,利用空間來換時間,作到了時間複雜度 O(n·log(n)) 的同時保持了穩定。

這讓它在一些更考慮排序效率和穩定性,次考慮存儲空間的場合很是適用(如數據庫內排序,和堆排序相比,歸併排序的穩定是優勢)。而且歸併排序很是適合於鏈表排序

屬性

  • 穩定 (O(n·log(n)) 時間複雜度的排序算法中,歸併排序是惟一穩定的)
  • 時間複雜度 O(n·log(n))
  • 對於數組須要 Θ(n) 的額外空間 注意:歸併排序須要額外的空間,這是它的不完美之處
  • 對於鏈表須要 O(log(n)) 的額外空間,因此歸併排序很是適合列表的排序
  • Does not require random access to data 由於這個特色,歸併排序很適合用來排序列表

核心概念

  • 分而治之的思想
  • 空間換時間,而且穩定保持穩定性這一點是它的亮點
  • 二分思想

初版:基本實現

以迭代的方式來實現(但要注意防止函數調用過深致使 JavaScript 的運行棧溢出):

function mergeSort(arr) {
  const len = arr.length;

  if (len < 2) { return arr; }

  const mid = Math.floor(len / 2);
  const left = arr.slice(0, mid);
  const right = arr.slice(mid);

  return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right) {
  const result = [];

  while (left.length > 0 && right.length > 0) {
    result.push(left[0] <= right[0] ? left.shift() : right.shift());
  }

  return result.concat(left, right);
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(mergeSort(arr));
複製代碼

第二版:空間優化

array.splice 取代 array.slice,減小一半的空間消耗。

function mergeSort2(arr) {
  const len = arr.length;

  if (len < 2) { return arr; }

  const mid = Math.floor(len / 2);
  const left = arr.splice(0, mid);
  const right = arr;

  return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right) {
  const result = [];

  while (left.length > 0 && right.length > 0) {
    result.push(left[0] <= right[0] ? left.shift() : right.shift());
  }

  return result.concat(left, right);
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(mergeSort2(arr));
複製代碼

堆排序 Heap Sort

簡明解釋

堆排序能夠認爲是選擇排序的改進版,像選擇排序同樣將輸入劃分爲已排序和待排序

不同的是堆排序利用堆這種近似徹底二叉樹的良好的數據結構來實現排序,本質上使用了二分的思想

  1. 先將全部的數據堆化
  2. 而後移動 arr[0] 到數組末尾(已排序區域)
  3. 再從新堆化,依次這樣循環來排序。

利用堆這種良好的數據結構,它在擁有良好的可預測性的同時(無論輸入什麼都是 O(n·log(n)) 時間複雜度),但它的缺點也有:即不穩定,並且 O(n·log(n)) 的平均效率決定了它的效率不如快速排序。適用於數據庫內引擎排序(須要這樣的可預測性性能)。

屬性

  • 不穩定
  • O(n·log(n)) time

核心概念

  • 利用良好的數據結構——堆,來排序
  • 二分的思想
  • 選擇排序的改進版,繼承了"可預測性"(什麼數據輸入都爲 O(n·log(n) time)

初版:基本實現

function heapSort(arr) {
  let size = arr.length;

  // 初始化堆,i 從最後一個父節點開始調整,直到節點均調整完畢 
  for (let i = Math.floor(size / 2) - 1; i >= 0; i--) {
    heapify(arr, i, size);
  }
  // 堆排序:先將第一個元素和已拍好元素前一位做交換,再從新調整,直到排序完畢
  for (let i = size - 1; i > 0; i--) {
    swap(arr, 0, i);
    size -= 1;
    heapify(arr, 0, size);
  }

  return arr;
}

function heapify(arr, index, size) {
  let largest = index;
  let left = 2 * index + 1;
  let right = 2 * index + 2;

  if (left < size && arr[left] > arr[largest]) {
    largest = left;
  }
  if (right < size && arr[right] > arr[largest]) {
    largest = right;
  }
  if (largest !== index) {
    swap(arr, index, largest);
    heapify(arr, largest, size);
  }
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(heapSort(arr));
複製代碼

維基上給出的另外一個方法

wikipedia 上給出的方法於初版的區別在於維護堆性質時採用的方式不一樣,本質是同樣的:

function heapSort(arr) {
  const size = arr.length;

  // 初始化 heap,i 從最後一個父節點開始調整,直到節點均調整完畢 
  for (let i = Math.floor(size / 2) - 1; i >= 0; i--) {
    heapify(i, size);
  }
  // 堆排序:先將第一個元素和已拍好元素前一位做交換,再從新調整,直到排序完畢
  for (let i = size - 1; i > 0; i--) {
    swap(arr, 0, i);
    heapify(0, i);
  }

  return arr;
}

function heapify(start, end) {
  // 創建父節點下標和子節點下標
  const dad = start;
  let son = dad * 2 + 1;

  if (son >= end) { return 0; }

  if (son + 1 < end && arr[son] < arr[son + 1]){
    son += 1;
  }
  if (arr[dad] <= arr[son]) {
    swap(arr, dad, son);
    heapify(son, end);
  }

  return 0;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(heapSort(arr));
複製代碼

快速排序 Quick Sort

簡明解釋

  1. 從數列中挑出一個元素,稱爲"基準"(pivot),
  2. 從新排序數列,全部比基準值小的元素擺放在基準前面,全部比基準值大的元素擺在基準後面(相同的數能夠到任何一邊)。在這個分區結束以後,該基準就處於數列的中間位置。這個稱爲分區(partition)操做
  3. 遞歸地(recursively)把小於基準值元素的子數列和大於基準值元素的子數列排序。

屬性

  • 不穩定
  • O(n²) time, 可是一般都是 O(n·log(n)) time (或者更快)
  • O(log(n)) extra space

When implemented well, it can be about two or three times faster than its main competitors, merge sort and heap sort

核心概念

  • 使用了分而治之的思想

初版:基本實現

function quickSort(arr) {
  const pivot = arr[0];
  const left = [];
  const right = [];
  
  if (arr.length < 2) { return arr; }

  for (let i = 1, len = arr.length; i < len; i++) {
    arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i]);
  }

  return quickSort(left).concat([pivot], quickSort(right));
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(quickSort(arr));
複製代碼

第二版:函數式編程

函數式編程:結構清晰,一目瞭然。

function quickSort2(arr) {
  const pivot = arr.shift();
  const left = [];
  const right = [];

  if (arr.length < 2) { return arr; }

  arr.forEach((element) => {
    element < pivot ? left.push(element) : right.push(element);
  });

  return quickSort2(left).concat([pivot], quickSort2(right));
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(quickSort2(arr));
複製代碼

第三版:in-place

等等,有沒有以爲第1、二版中的代碼雖然看起來簡潔,可是卻對空間消耗很大呢?

由此有了 in-place 版本:

function quickSort3(arr, left = 0, right = arr.length - 1) {
  if (left < right) {
    const pivot = partition(arr, left, right);

    quickSort3(arr, left, pivot - 1);
    quickSort3(arr, pivot + 1, right);
  }
  return arr;
}

function partition (arr, left ,right) {
  let pivot = left; // 以第一個元素爲 pivot

  for (let i = left + 1; i <= right; i++) {
    if (arr[i] < arr[left]) { 
      swap(arr, i, pivot);
      pivot += 1;
    }
  }
  swap(arr, left, pivot); //將 pivot 值移至中間
  
  return pivot;
}

// test
const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(quickSort3(arr));
複製代碼

第四版:關於 pivot 的選取

這一版的亮點是 pivot 的選取,再也不是簡單的取 arr[0],而是:

const pivot = left + Math.ceil((right - left) * 0.5)
複製代碼

很是感謝評論區的大神 @Chris_dong 的解釋:

const pivot = left + Math.ceil((right - left) * 0.5) => (去掉MAth.ceil是否是很好理解) left + (right - left) * 0.5 => (right + left) * 0.5

看到真相的我眼淚掉下來,原來是取中間值。。。

由此有了如下版本:

function quickSort4(arr, left = 0, right = arr.length - 1) {
  if (left < right) {
    // const pivot = left + Math.ceil((right - left) * 0.5);
    const pivot = Math.floor((right + left) / 2);
    const newPivot = partition(arr, pivot, left, right);

    quickSort4(arr, left, newPivot - 1);
    quickSort4(arr, newPivot + 1, right);
  }

  return arr;
}

function partition(arr, pivot, left, right) {
  const pivotValue = arr[pivot];
  let newPivot = left;

  swap(arr, pivot, right);
  for (let i = left; i < right; i++) {
    if (arr[i] < pivotValue) {
      swap(arr, i, newPivot);
      newPivot += 1;
    }
  }
  swap(arr, right, newPivot);

  return newPivot;
}

const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
console.log(quickSort4(arr));
複製代碼

總結 & 答疑

提出幾個問題,能夠當作自我檢測:

  • 數據幾乎快排序完成時?

插入排序不解釋

  • 數據量小,對效率要求不高,代碼簡單時?

性能大小:希爾排序 > 插入排序 > 冒泡排序 > 選擇排序

  • 數據量大,要求穩定的效率(不會像快速排序同樣有 O(n²) 的狀況)(如數據庫中)?

堆排序

  • 數據量大,要求效率高,並且要穩定?

歸併排序

  • 數據量大,要求最好的平均效率?

性能大小:快速排序 > 堆排序 > 歸併排序

由於雖然堆排序作到了 O(n·log(n),而快速排序的最差狀況是 O(n²),可是快速排序的絕大部分時間的效率比 O(n·log(n) 還要快,因此快速排序真的無愧於它的名字。(十分快速)

  • 選擇排序絕對沒用嗎?

選擇排序只須要 O(n) 次交換,這一點它完爆冒泡排序。


答疑:

  • 博主你的代碼從哪裏抄的?

都是博主含辛茹苦、遍查資料、一行一行含淚認真碼出來的。參考&感謝 部分裏列出了全部來源地址:)

  • 爲何不用 ES5 寫呢?

實際上這篇文章繼承於優雅的 JavaScript 排序算法 。這一版是上通常的姐妹版(解釋精簡,使用 ES6 使代碼更精簡),若想參考英文引用、ES5 代碼、過程詳細解釋能夠參考初版

ES6 是爲了更強大的表現力,從而讓咱們更加關注於算法的內在,不被一些邊邊角角所束縛。

附錄:代碼風格

博主一貫認爲是有【代碼品味】這種東西存在的,能夠從以前的這篇文章從 shuffle 看代碼品味一窺端倪。

再次表達一下本身的觀點:

  • 軟件開發不是教條
  • 代碼品味沒有高低

可是追求的最終目的是一致的:好讀又簡潔,穩定易維護

爲了這個目標我作了這些努力:

  • 注重可讀性變量名*:如 preIndex, temp, size
  • 一目瞭然的函數結構
    function () {
      const/let ...;

      function body

      return...;
    };
  • 在計算 len / 2 的取整時爲了可讀性選擇了 Math.floor(len / 2),沒有選擇 len >> 1parseInt(len / 2, 10)
  • 注意區分 forwhile 的使用場景,具體能夠看這個問題的答案:
    https://stackoverflow.com/questions/39969145/while-loops-vs-for-loops-in-JavaScript;
  • 爲了簡單直觀,未使用 Array.isArray()Object.prototype.toString.call()typeOf, instanceOf 來檢查 arr 是否是數組類型,默認 arr 就是數組類型;
  • 使用三元運算符 ( ? : ) 來減小 if 的嵌套,提升代碼可讀性
  • 自增(++)和自減(--)運算符使用 +=-= 代替 (for 中的最後一個條件除外);
  • 使用 ES6 中的默認參數方式(快速排序中)簡化代碼,將關鍵邏輯突出;
  • Eslint + Airbnb 全局控制代碼風格;
  • 在風格以外加上本身的喜愛,好比用 function 聲明函數,具體緣由見:從 shuffle 看代碼品味

這是個人品味,你的呢:)

引用 & 感謝

  • Wikipedia about Sorting Algorithms (English & Chinese),參考了其中的概念解釋和代碼實現(進行修改),:
    https://en.wikipedia.org/wiki/Sorting_algorithm
  • Sorting Algorithms visualization (English),強烈推薦看對理解排序算法運行的過程頗有幫助:
    https://www.toptal.com/developers/sorting-algorithms
  • stackOverFlow Sorting Algorithms 參考其中排序算法相關的答案來輔助理解:
    https://www.quora.com/Why-do-we-need-so-many-sorting-algorithms
    https://www.quora.com/Why-is-shell-sort-faster-than-insertion-sort-and-bubble-sort
    https://www.quora.com/Why-is-heap-sort-used
    還有一些就不一一列舉了...
  • damonare's demonstrate (Chinese),參考了其中冒泡排序 1,2,3;參考其中的動圖來輔助理解; 選擇排序;歸併排序的實現;並進行修改:
    https://github.com/damonare/Sorts
  • 參考其中的
    quick sort in-place edition https://gist.github.com/paullewis/1981455,並進行修改
  • hustcc's gitbook (Chinese),參考了其中的快速排序,希爾排序的實現,並進行修改https://sort.hust.cc/6.quickSort.html
相關文章
相關標籤/搜索