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

wjaning發表於2021-09-09

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

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

簡單插入排序

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

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 的水平。

總結

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

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2983/viewspace-2797381/,如需轉載,請註明出處,否則將追究法律責任。

相關文章