js 排序算法之快速排序

快速排序是一種劃分交換排序。它採用了一種分治的策略,一般稱其爲分治法。算法

分治法的基本思想是:將原問題分解爲若干個規模更小但結構與原問題類似的子問題。遞歸地解這些子問題,而後將這些子問題的解組合爲原問題的解。數組

快速排序基於冒泡、遞歸分治。他在大數據狀況下是最快的排序算法之一,平均事件複雜度很低並且前面的係數很小,在大量隨機輸入的狀況下最壞狀況出現的機率是極小的。緩存

最壞時間複雜度:O($n^2$) 當選擇的基準值爲最大值或最小值時
穩定性:不穩定
平均時間複雜度:O(n*$log_2$n)

阮一峯版 內存佔用較多

function quickSort(arr) {
    if(arr.length <= 1) {
        return arr;
    }
    let pivotIndex = Math.floor(arr.length / 2);
    let pivot = arr.splice(pivotIndex, 1)[0];
//  let pivot = arr.splice(pivotIndex, 1);  3 < [9] //true
    let left = [];
    let right = [];
    for(let 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));
}
上面簡單版本的缺點是,它須要Ω(n)的額外存儲空間,也就跟歸併排序同樣很差。額外須要的存儲器空間配置,在實際上的實現,也會極度影響速度和高速緩存的性能。

真正的快排

按照維基百科中的原地(in-place)分區版本,實現快速排序方法以下:性能

function quickSort(arr) {
    function swap(arr, i, k) {
        let temp = arr[i];
        arr[i] = arr[k];
        arr[k] = temp;
    }
    // 數組分區,左小右大
    function partition(arr, left, right) {
        let storeIndex = left;
        let pivot = arr[right]; // 直接選最右邊的元素爲基準元素
        for(let i = left; i < right; i++) {
            if(arr[i] < pivot) {
                swap(arr, storeIndex, i);
                storeIndex++; // 交換位置後,storeIndex 自增 1,表明下一個可能要交換的位置
            } 
        }
        swap(arr, storeIndex, right); // 將基準元素放置到最後的正確位置上
        return storeIndex;
    }
    function sort(arr, left, right) {
        if(left > right) {
            return;
        }
        let storeIndex = partition(arr, left, right);
        sort(arr, left, storeIndex - 1);
        sort(arr, storeIndex + 1, right);
    }
    sort(arr, 0, arr.length - 1);
    return arr;
}

利用分治法來處理快排,主要的思想是:大數據

  1. 在數據集之中,選擇一個元素做爲」基準」(pivot)。
  2. 全部小於」基準」的元素,都移到」基準」的左邊;全部大於」基準」的元素,都移到」基準」的右邊。這個操做稱爲分區 (partition) 操做,分區操做結束後,基準元素所處的位置就是數組最終排序後它的位置。
  3. 對」基準」左邊和右邊的兩個子集,不斷重複第一步和第二步,直到全部子集只剩下一個元素爲止。

步驟:ui

首先,把基準元素移到結尾(若是直接選擇最後一個元素爲基準元素,那就不用移動);
而後從左到右(除了最後的基準元素),循環移動小於等於基準元素的元素到數組的開頭,每次移動 storeIndex 自增 1,表示下一個小於基準元素將要移動到的位置;
循環結束後 storeIndex 所表明的的位置就是基準元素的全部擺放的位置;因此最後將基準元素所在位置(這裏是 right)與 storeIndex 所表明的的位置的元素交換位置。
完成一次分區;.net

tips:這裏爲何要把基準元素放到數組的最後一個元素的位置上,是爲了方便對數組中除了基準元素之外的全部元素進行遍歷,並方便在找到基準元素的排序位置 storeIndex 後進行兩兩交換。假若不如此,須要將該基準元素從原數組中取出來(相似阮一峯版作法arr.splice(pivotIndex, 1)),循環遍歷完全部除基準元素外的元素後,找到基準元素的最後排序位置 storeIndex後,須要將基準元素插入進來(用到插入排序的思想),顯然這種方式較爲複雜。code

因此通常選取了除數組最後一個元素爲基準元素後,會將該基準元素換到最後一個元素上;這裏便直接選取數組中最後一個元素爲基準元素,對整個數組進行分區操做[0~arr.length-1].固然也能夠只對數組中某一連續數組元素進行分區,即只對數組中這一小部分元素進行排序sort(arr, start, end);blog

function quickSort(arr, start, end) {
    function swap(arr, i, k) {
        let temp = arr[i];
        arr[i] = arr[k];
        arr[k] = temp;
    }
    // 數組分區,左小右大
    function partition(arr, left, right) {
        let storeIndex = left;
        let pivot = arr[right]; // 直接選最右邊的元素爲基準元素
        for(let i = left; i < right; i++) {
            if(arr[i] < pivot) {
                swap(arr, storeIndex, i);
                storeIndex++; // 交換位置後,storeIndex 自增 1,表明下一個可能要交換的位置
            } 
        }
        swap(arr, storeIndex, right); // 將基準元素放置到最後的正確位置上
        return storeIndex;
    }
    function sort(arr, left, right) {
        if(left > right) {
            return;
        }
        let storeIndex = partition(arr, left, right);
        sort(arr, left, storeIndex - 1);
        sort(arr, storeIndex + 1, right);
    }
    sort(arr, start, end);
    return arr;
}
quickSort([3,7,8,5,2,1,9,5,4], 3, 7) // 只對部分元素排序

References

JS實現快速排序算法的詳細解釋
常見排序算法 - 快速排序 (Quick Sort)
算法的時間複雜度和空間複雜度-總結排序

相關文章
相關標籤/搜索