算法入門——排序算法

選擇排序

如果有一組數(shù),按從大到小排列,遍歷列表,找出最大的數(shù)并添加到一個新列表,再次這樣做,找出第二大的數(shù),以此類推,這樣便可得到一個有序列表。

示例

// 找出最大元素
function findLargest(arr){
    let largest = arr[0];
    let largestIndex = 0;
    for (let i in arr){
        if (arr[i] > largest) {
            largest = arr[i];
            largestIndex = i;
        }
    } 
    return largestIndex;
}

// 對數(shù)組進(jìn)行排序 
function selectionSort(arr){ 
    let newArr = []
    while(arr.length > 0){
        let largest = findLargest(arr);
        newArr.push(arr.splice(largest, 1)[0])
    }
    return newArr
}
console.log(selectionSort([5, 3, 6, 2, 10]));

選擇排序需要的總時間為 O(n × n),即O(n2)。

需要檢查的元素數(shù)越來越少

隨著排序的進(jìn)行,每次需要檢查的元素數(shù)在逐漸減少,最后一次需要檢查的元素都只有一 個。既然如此,運(yùn)行時間怎么還是O(n2)呢?這個問題問得好,這與大O表示法中的常數(shù)相關(guān),后面有機(jī)會我還詳細(xì)解釋一下。

沒錯,并非每次都需要檢查n個元素。第一次需要檢查n個元素,但隨后檢查的元素 數(shù)依次為n - 1, n - 2, ..., 2和1。平均每次檢查的元素數(shù)為1/2 × n,因此運(yùn)行時間為O(n × 1/2 × n)。 但大O表示法省略諸如1/2這樣的常數(shù),因此簡單地寫 作O(n × n)或O(n2)。

選擇排序是一種靈巧的算法,但其速度不是很快。

快速排序

分而治之

在介紹快速排序之前先介紹一下“分而治之”(divide and conquer,D&C),一種著名的遞歸問題解決方法。
使用D&C解決問題的過程包括兩個步驟:

  • 找出基線條件,這種條件必須盡可能簡單。
  • 不斷將問題分解(或者說縮小規(guī)模),直到符合基線條件。

D&C 的工作原理:

  • 找出簡單的基線條件。
  • 確定如何縮小問題的規(guī)模,使其符合基線條件。

D&C 并非可用于解決問題的算法,而是一種解決問題的思路。

快速排序

快速排序是一種常用的排序算法,比選擇排序快得多。例如,C 語言標(biāo)準(zhǔn)庫中的函數(shù) qsort 實(shí)現(xiàn)的就是快速排序??焖倥判蛞彩褂昧?D&C。
首先根據(jù) D&C 的步驟,我們需要找到基線條件:數(shù)組為空或只包含一個元素。在這種情況下,只需原樣返回數(shù)組——根本就不用排序。然后縮小問題:分解數(shù)組,直到滿足基線條件。從數(shù)組中選一個元素,作為基準(zhǔn)值(pivot)。
接下來,找出比基準(zhǔn)值小的元素以及比基準(zhǔn)值大的元素,這個過程被稱為分區(qū)(partitioning),會得到:

  • 一個由所有小于基準(zhǔn)值的數(shù)字組成的子數(shù)組
  • 基準(zhǔn)值
  • 一個由所有大于基準(zhǔn)值的數(shù)組組成的子數(shù)組

我們將得到的兩個子數(shù)組再進(jìn)行上述操作,直到都滿足基線條件。這樣我們便得到一個有序列表。

function quicksort(arr) {
    if (arr.length < 2){
        return arr
    } else {
        // 遞歸條件
        let pivot = arr[0];
        let less = [];
        let greater = [];
        for (let i in arr){
            if (arr[i] < pivot){
                less.push(arr[i])
            } else if (arr[i] > pivot){
                greater.push(arr[i])
            } 
        } 
        return [...quicksort(less), pivot, ...quicksort(greater) ]
    }
}
console.log(quicksort([5, 3, 6, 2, 10]));

分解數(shù)組的過程使用了歸納證明的思想,如果是兩個元素的數(shù)組我們可以輕易對其排序,那如果是三個元素呢,我們可以將其拆分為兩個元素的數(shù)組,和一個元素的數(shù)組,以此類推我們可以對任意長度數(shù)組進(jìn)行拆分,那么我們可以任意長度數(shù)組排序。

歸納證明

歸納證明是一種證明算法行之有效的方式,它分兩步:基線 條件和歸納條件。是不是有點(diǎn)似曾相識的感覺?例如,假設(shè)我要證明我能爬到梯子的最上面。 遞歸條件是這樣的:如果我站在一個橫檔上,就能將腳放到下一個橫檔上。換言之,如果我站 在第二個橫檔上,就能爬到第三個橫檔。這就是歸納條件。而基線條件是這樣的,即我已經(jīng)站 在第一個橫檔上。因此,通過每次爬一個橫檔,我就能爬到梯子最頂端。

對于快速排序,可使用類似的推理。在基線條件中,我證明這種算法對空數(shù)組或包含一個 元素的數(shù)組管用。在歸納條件中,我證明如果快速排序?qū)Π粋€元素的數(shù)組管用,對包含兩 個元素的數(shù)組也將管用;如果它對包含兩個元素的數(shù)組管用,對包含三個元素的數(shù)組也將管用, 以此類推。因此,我可以說,快速排序?qū)θ魏伍L度的數(shù)組都管用。這里不再深入討論歸納證明, 但它很有趣,并與D&C協(xié)同發(fā)揮作用。

快速排序的速度取決于選擇的基準(zhǔn)值,在最糟情況下,其運(yùn)行時間為O(n2)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文的最新版本位于:https://github.com/iwhales/algorithms_notes轉(zhuǎn)載請注...
    import_hello閱讀 1,786評論 1 17
  • 概述 排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    閑云清煙閱讀 820評論 0 6
  • 概述 排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    zwb_jianshu閱讀 1,423評論 0 0
  • 本文首發(fā)于我的個人博客:尾尾部落 排序算法是最經(jīng)典的算法知識。因為其實(shí)現(xiàn)代碼短,應(yīng)該廣,在面試中經(jīng)常會問到排序算法...
    繁著閱讀 4,681評論 3 118
  • 概述 排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    蟻前閱讀 5,301評論 0 52

友情鏈接更多精彩內(nèi)容