排序算法:插入排序

前言

上一次,我們介紹了排序算法中“龜速三兄弟”的大哥“冒泡排序”。今天,我們繼續介紹“龜速三兄弟”中的二哥——“插入排序”。“冒泡排序”的過程和代碼相信大多數人都比較熟悉,但是“插入排序”就不見得了。由於同樣是“龜速三兄弟”中的一員,但是處理過程沒有“冒泡排序”那麼簡單明瞭,因此有不少人可能都沒接觸過“插入排序”,本文將通過例子來介紹“插入排序”的完整過程。

 

基本思想

將一個數插入一個已經排好序的數據中。

  1. 第一次循環時,從第2個數開始處理。我們將第1個數作爲已經排好序的數據:當第2個數 > 第1個數時,將第2個數放在第1個數後面一個位置;否則,將第2個數放在第1個數前面。此時,前兩個數形成了一個有序的數據。
  2. 第二次循環時,我們處理第3個數。此時,前兩個數形成了一個有序的數據:首先比較第3個數和第2個數,當第3個數 > 第2個數時,將第3個數放在第2個數後面一個位置並結束此次循環;否則,再和第1個數比較。如果第3個數 > 第1個數,則將第3個數插入第1個數和第2個數中間;否則,第3個數 < 第1個數,則將第3個數放在第1個數前面。此時,前三個數形成了一個有序的數據。
  3. 後續的數據同理處理,直至結束。

 

例子

下面通過一個例子來看看插入排序是怎麼工作的。原數組如下。

第一次循環:

1.從第2個數開始處理,即array[1]。比較array[1]和arary[0],即15和19。因爲15 < 19,所以將arary[1]放在array[0]的前面,放完後的數組如下。

由於是從第2個數開始處理,一直處理到最後一個數。因此,最外層的循環可以寫成如下:

for (int i = 1; i < array.length; i++)

至此,第一次循環結束。

 

第二次循環:

1.處理第3個數,即array[2]。首先比較array[2]和array[1],即37和19。因爲37 > 19,並且前面2個數經過第一次循環後是有序的。因此,直接將37放在array[1]後面的1個位置即array[2]原位置,放完後的數組如下。

至此,第二次循環結束。

 

第三次循環:

1.處理第4個數,即array[3]。首先比較array[3]和array[2],即12和37。因爲12 < 37,所以37此時在前4個數中可以確定是最大的。因此此時array[3]是37的正確位置,但是array[3]此時被12佔着,所以我們需要用一個臨時變量temp存儲arary[3]的值,然後將37填入array[3],填完後的數組如下。

結合比較語句,此時的代碼可以寫成如下,在當前循環 i = 3。

if (array[i] < array[i - 1]) {
    // 臨時變量存儲array[i]的值
    int temp = array[i];  
    // array[i - 1]填入此時屬於它的位置
    array[i] = array[i - 1];  
}

填充完array[3]後,我們需要將下標向前移動一個位置,好將temp(原來的array[3])跟array[1]進行比較,但是此時下標 i 代表着循環的位置不能移動。因此,我們需要再定義一個變量 j 來記錄比較的位置,因此將上述代碼優化成如下:

// 當array[i]比arra[i - 1]小時才進入處理
if (array[i] < array[i - 1]) {
    // 臨時變量存儲array[i]的值
    int temp = array[i];  
    // 定義變量j記錄比較的位置
    int j = i;  
    // 將比temp大的數往後挪一個位置,爲temp騰出一個合適位置
    while (j > 0 && temp < array[j - 1]) {  
        array[j] = array[j - 1];  
        j--;    // 填充完後, 繼續向前比較 
    }  
}

 

2.此時 j 移動到下標2,比較temp和array[1],即12和19。因爲12 < 19,並且原來array[2]位置的數37此時已經填入array[3],因此此時array[2]相當於一個坑,直接將19填入即可。將19填入array[2]時,array[1]又形成了一個新的坑,此時的數組如下。

填完後,將下標 j 繼續向前移動一個位置。

 

3.此時 j 移動到下標1,此時比較temp和array[0],即12和15。因爲12 < 15,並且原來array[1]位置的數19此時已經填入array[2],因此直接將15填入array[1],此時array[0]又形成了一個新的坑,此時的數組如下。

4.此時 j 移動到下標0,已經沒有前面的數可以比較了。因此,array[0]即爲temp的位置,將temp填入array[0]後結束此次循環,此時的數組如下。

結合上面的代碼,將temp填入對應位置的代碼也加上後,代碼如下:

// 當array[i]比arra[i - 1]小時才進入處理
if (array[i] < array[i - 1]) {
    // 臨時變量存儲array[i]的值
    int temp = array[i];  
    // 從當前位置開始處理
    int j = i;  
    // 將比temp大的數往後挪一個位置,爲temp騰出一個合適位置
    while (j > 0 && temp < array[j - 1]) {  
        array[j] = array[j - 1];  
        j--;    // 填充完後, 繼續向前比較 
    }  
    // 將temp放在屬於它的位置上
    array[j] = temp;  
}  

 

第四次循環

1.處理第5個數,即array[4]。首先比較array[4]和array[3],即25和37。因爲25 < 37,所以將25賦值給temp,並將37填入array[4]的位置,並將下標 j 向前移動一個位置,此時的數組如下。

2.此時 j 移動到下標3,此時比較temp和array[2],即25和19。因爲25 > 19,所以array[3]即爲25的位置,將25填入array[3]後結束此次循環。

至此,整個排序過程結束。

 

完整過程

最後我們通過以下動圖來看插入排序的整個過程,圖中紅色的柱子爲當前要插入的數,橘黃色的柱子爲當前已經排好序的數據,綠色的柱子爲正在跟紅色的柱子比較的數,淺藍色的柱子爲還未插入的數。

 

整合

上文的例子中已經基本將邏輯代碼都寫清了,我們將外層循環和裏面的處理邏輯代碼結合到一起,代碼如下。

 

時間複雜度

在最壞的情況下,即整個數組是倒序的,比較次數 = 1 + 2 + 3 + ... + (n - 2) + (n - 1) = n * (n - 1) / 2,此時的時間複雜度爲:O(n^2)。

在最好的情況下,即整個數組是正序的,比較次數 = n - 1,此時的時間複雜度爲:O(n)。

 

使用場景

插入排序和冒泡排序一樣。在進行量比較大的排序時,最好不要使用。在1000個數字以內的排序,用插入排序是可以接受的。1000個隨機數使用插入排序耗時一般在5毫秒以內,但是當數字個數達到10000個時,耗時會達到30+毫秒,有比較明顯的耗時,需要慎重使用。

 

相關閱讀

排序算法:冒泡排序

—————END—————

 

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