【PHP數據結構】插入類排序:簡單插入、希爾排序

總算進入我們的排序相關算法的學習了。相信不管是系統學習過的還是沒有系統學習過算法的朋友都會聽說過許多非常出名的排序算法,當然,我們今天入門的內容並不是直接先從最常見的那個算法說起,而是按照一定的規則一個一個的介紹。

首先,我們要介紹的排序算法是插入類型的排序算法。顧名思義,插入排序就是將無序的一個或幾個記錄“插入”到有序的序列中,比較典型的例子就是簡單插入排序和希爾排序。

簡單插入排序

簡單插入排序,也可以叫做直接插入排序。還是先看代碼,再來進行下一步的解釋。

function InsertSort($arr)
{
    $n = count($arr);
    for ($i = 1; $i < $n; $i++) { // 開始循環,從第二個元素開始,下標爲 1 的
        $tmp = $arr[$i]; // 取出未排序序列第一個元素
        for ($j = $i; $j > 0 && $arr[$j - 1] > $tmp; $j--) { // 判斷從當前下標開始向前判斷,如果前一個比當前元素大
            $arr[$j] = $arr[$j - 1]; // 依次移動元素
        }
        // 將元素放到合適的位置
        $arr[$j] = $tmp;
    }
    echo implode(', ', $arr), PHP_EOL;
}

InsertSort($numbers);

// 49, 38, 65, 97, 76, 13, 27, 49
// 13, 27, 38, 49, 49, 65, 76, 97

代碼量不多吧,其實也非常好理解。我們就拿測試數據的前兩個數來簡單地說明一下。

首先,第一個循環是從 1 開始的,也就是說,第一個取出的未排序序列元素是 tmp = arr[1] ,也就是當前的 tmp = 38 。

然後開始循環,當前的循環是判斷 j-1 的元素是否比當前這個 tmp 元素大,如果是的話,進入循環體,arr[1] = arr[0] 。到目前爲止,arr[0] 和 arr[1] 現在都是 49 。整個序列是 49,49,65,……

最後讓 arr[0] = $tmp ,也就是等於 38 。(循環的時候 j-- 了)。整個序列是 38,49,65,……

通過下面這張圖片,我們可以更清楚地看明白整個序列完成排序的過程。

從上面的步驟可以看出,簡單插入排序就是從一邊開始,先讓前面的數據逐步有序的過程。從代碼中就可以看出,它是不斷地內部地循環中進行 j 的遞減,與前面有序的數列進行比對,當發現了自己合適的位置之後,就將數據放到這個位置上。

從代碼和我們的分析來看,簡單插入排序的時間複雜度是 O(n2) 。同時,它是屬於穩定的排序,什麼叫穩定排序呢?細心的同學應該發現了,在我們的測試代碼中,有兩個相同的數據,也就是 49 。穩定的意思就是相同的數據在排序前後的位置不會發生改變,前面的 49 依然是在後面的 49 前面。這就是排序的穩定性。

另外,簡單插入排序比較適合初始記錄基本有序的情況,當初始記錄無序,n 較大時,這個算法的時間複雜度會比較高,不太適合採用。

希爾排序

簡單插入排序很好理解吧,希爾排序又是什麼鬼呢?彆着急,從這個名字我們是看不出什麼端倪的,因爲這個排序的名字是以它的發現者命名的。實際上,希爾排序還是一個插入排序的算法。

上文中說過,簡單插入排序適合基本有序的情況,而希爾排序就是爲了提升簡單插入排序的效率而出現的,它主要目的是減少排序的 n 的大小以及通過幾次排序就讓數據形成基本有序的格式。

對於這個算法,我們不能先上代碼了,先來看圖吧。

看明白了嗎?我們其實是將數據進行分組了,每次分組是以一定的增量爲基礎的,比如我們這個示意圖中就是第一次以 5 爲增量進行排序,第二次是以 3 爲增量。這樣第三次排序的時候,增量爲 1 ,也就成爲一個普通的簡單插入排序了。一會我們代碼中就會體現出來。

還是按增量爲迭代次序進行這三趟排序的具體分析吧:

1)第一次迭代的時候,我們將分組增量設置爲 5 ,這時分別有三組數據,也就是 49 和 13,38 和 27,65 和 49 ,然後對這三組數據進行簡單插入排序,之後的數組結果是 13、27、49、97、76、49、38、65 。

2)第二次迭代,分組增量爲 3,這時就分成了兩組,每組三個數據,分別是 13、97、38 爲一組,另一組是 27 、76 、65 。對這兩組數據進行簡單插入排序之後更新數組結果爲 13、27、49、38、65、49、97、76 。

3)其實從兩次分組排序之後就可以看出,這個數組已經基本有序了。這時最後就是以分組增量 1 再次進行簡單插入排序。說白了,最後這一步就是一個普通的簡單插入排序的過程了。

分步驟講解之後是不是清楚很多了,再重複一篇,希爾排序其實就是按分組來一次大範圍的插入排序,最後一步步縮小到只有 1 次增量的簡單插入排序了。我們再來看看代碼吧:

function ShellSort($arr)
{
    $n = count($arr);
    $sedgewick = [5, 3, 1];

    // 初始的增量值不能超過待排序列的長度
    for ($si = 0; $sedgewick[$si] >= $n; $si++); 

    // 開始分組循環,依次按照 5 、3 、 1 進行分組
    for ($d = $sedgewick[$si]; $d > 0; $d = $sedgewick[++$si]) {
        // 獲取當前的分組數量
        for ($p = $d; $p < $n; $p++) {
            $tmp = $arr[$p];
            // 插入排序開始,在當前組內
            for ($i = $p; $i >= $d && $arr[$i - $d] > $tmp; $i -= $d) {
                $arr[$i] = $arr[$i - $d];
            }
            $arr[$i] = $tmp;
        }
    }
    echo implode(', ', $arr), PHP_EOL;
}
ShellSort($numbers);

看着代碼貌似有三層 for 循環呀,它哪裏提升了效率了呢?其實希爾排序的效率提升確實是有限的,它其實是通過前幾次的分組讓數據先基本有序。而在分組的狀態中,數據比較的數量並不會達到 n 的級別。當最後一次進行簡單排序的時候,整個數據已經是基本有序了,在這種情況下交換的次數明顯也會減少很多,所以它的時間複雜度在理想狀態下可以減少到 O(log2n)2 的水平。

總結

排序的入門餐怎麼樣?我們可不是直接就拿爛大街的冒泡和快排上手的吧。不出名不意味着不會用到,比如我面試的時候曾經有個公司就是在面試題上寫明瞭不能使用冒泡和快排。這時候,我相信簡單插入排序直觀好理解的特性一定就能幫助我們度過這種面試難關了哦!

測試代碼:

https://github.com/zhangyue0503/Data-structure-and-algorithm/blob/master/7.排序/source/7.1插入類排序:簡單插入、希爾排序.php

參考文檔:

本文示例選自 《數據結構》第二版,嚴蔚敏

《數據結構》第二版,陳越

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章