js算法-快速排序(Quicksort)

快速排序(英語:Quicksort),又稱劃分交換排序(partition-exchange sort),簡稱快排,一種排序算法,最先由東尼·霍爾提出。在平均情況下,排序n個項目要O(nLogn)次比較。在最壞情況下則須要O(n^2)次比較,但這種情況並不常見。事實上,快速排序O(nLogn)一般明顯比其餘算法更快,由於它的內部循環(inner loop)能夠在大部分的架構上頗有效率地達成

快速排序可能你們都學過,在面試中也常常會遇到,哪怕你是作前端的也須要會寫,這裏會列舉兩種不一樣的快排代碼進行分析前端

快速排序的3個基本步驟:

  1. 從數組中選擇一個元素做爲基準點
  2. 排序數組,全部比基準值小的元素擺放在左邊,而大於基準值的擺放在右邊。每次分割結束之後基準值會插入到中間去。
  3. 最後利用遞歸,將擺放在左邊的數組和右邊的數組在進行一次上述的1和2操做。

爲了更深刻的理解,能夠看下面這張圖面試

圖片描述

咱們根據上面這張圖,來用文字描述一下算法

  1. 選擇左右邊的元素爲基準數,7
  2. 將小於7的放在左邊,大於7的放在右邊,而後將基準數放到中間
  3. 而後再重複操做從左邊的數組選擇一個基準點2
  4. 3比2大則放到基準樹的右邊
  5. 右邊的數組也是同樣選擇12做爲基準數,15比12大因此放到了12的右邊
  6. 最後出來的結果就是從左到右 2 ,3,7,12,15了

以上就是快速排序基本的一個實現思想。segmentfault

快速排序實現方式一

這是我最近看到的一種快排代碼數組

var quickSort = function(arr) {
  if (arr.length <= 1) {
    return arr;
  }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];

  for (var i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
};

以上代碼的實現方式是,選擇一箇中間的數字爲基準點,用兩個數組分別去保存比基準數小的值,和比基準數大的值,最後遞歸左邊的數組和右邊的數組,用concat去作一個數組的合併。數據結構

對於這段代碼的分析:
缺點:架構

  • 獲取基準點使用了一個splice操做,在js中splice會對數組進行一次拷貝的操做,而它最壞的狀況下複雜度爲O(n),而O(n)表明着針對數組規模的大小進行了一次循環操做。
  • 首先咱們每次執行都會使用到兩個數組空間,產生空間複雜度。
  • concat操做會對數組進行一次拷貝,而它的複雜度也會是O(n)
  • 對大量數據的排序來講相對會比較慢

優勢:ide

  • 代碼簡單明瞭,可讀性強,易於理解
  • 很是適合用於面試筆試題

那麼咱們接下來用另一種方式去實現快速排序函數

快速排序的實現方式二

圖片描述

從上面這張圖,咱們用一個指針i去作了一個分割oop

  • 初始化i = -1
  • 循環數組,找到比支點小的數就將i向右移動一個位置,同時與下標i交換位置
  • 循環結束後,最後將支點與i+1位置的元素進行交換位置
  • 最後咱們會獲得一個由i指針做爲分界點,分割成從下標0-i,和 i+1到最後一個元素。

下面咱們來看一下代碼的實現,整個代碼分紅三部分,數組交換,拆分,qsort(主函數)三個部分

先寫最簡單的數組交換吧,這個你們應該都懂

function swap(A, i ,j){
        const t = A[i];
        A[i] = A[j];
        A[j] = t;
    }

下面是拆分的過程,其實就是對指針進行移動,找到最後指針所指向的位置

/**
 * 
 * @param {*} A  數組
 * @param {*} p  起始下標
 * @param {*} r  結束下標 + 1
 */
 function dvide(A, p, r){
    // 基準點
    const pivot = A[r-1];
    
    // i初始化是-1,也就是起始下標的前一個
    let i = p - 1;
    
    // 循環
    for(let j = p; j < r-1; j++){
        // 若是比基準點小就i++,而後交換元素位置
        if(A[j] <= pivot){
            i++;
            swap(A, i, j);
        }
    }
    // 最後將基準點插入到i+1的位置
    swap(A, i+1, r-1);
    // 返回最終指針i的位置
    return i+1;
 }

主程序主要是經過遞歸去重複的調用進行拆分,一直拆分到只有一個數字。

/**
     * 
     * @param {*} A  數組
     * @param {*} p  起始下標
     * @param {*} r  結束下標 + 1
     */
    function qsort(A, p, r){
        r = r || A.length;
        if(p < r - 1){
            const q = divide(A, p, r);
            qsort(A, p, q);
            qsort(A, q + 1, r);
        }
        return A;
    }

完整代碼

function swap(A, i, j) {
  const t = A[i];
  A[i] = A[j];
  A[j] = t;
}

/**
 *
 * @param {*} A  數組
 * @param {*} p  起始下標
 * @param {*} r  結束下標 + 1
 */
function divide(A, p, r) {
  const x = A[r - 1];
  let i = p - 1;

  for (let j = p; j < r - 1; j++) {
    if (A[j] <= x) {
      i++;
      swap(A, i, j);
    }
  }

  swap(A, i + 1, r - 1);

  return i + 1;
}

/**
 * 
 * @param {*} A  數組
 * @param {*} p  起始下標
 * @param {*} r  結束下標 + 1
 */
function qsort(A, p = 0, r) {
  r = r || A.length;

  if (p < r - 1) {
    const q = divide(A, p, r);
    qsort(A, p, q);
    qsort(A, q + 1, r);
  }

  return A;
}

總結

第二段的排序算法咱們減小了兩個O(n)的操做,獲得了必定的性能上的提高,而第一種方法數據規模足夠大的狀況下會相對來講比較慢一些,快速排序在面試中也經常出現,爲了筆試更好寫一些可能會有更多的前端會選擇第一種方式,但也會有一些爲難人的面試官提出一些算法中的問題。而在實際的項目中,我以爲第一種方式能夠少用。

推薦

本人最近寫的關於數據結構系列以下,歡迎你們看看點個贊哈:
js數據結構-棧
js數據結構-鏈表
js數據結構-隊列
js數據結構-二叉樹(二叉堆)
js數據結構-二叉樹(二叉搜索樹)

相關文章
相關標籤/搜索