插入排序(insertion sort)是一種排序演算法,它將一個數列分成已排序與未排序兩個部分,透過不斷將未排序數列插入至已排序數列的正確位置達成,嗯…這個定義唸起來實在有些饒口,不如讓我們看個實例:
假設我們要由小到大排序數列:
1 | 25 49 22 33 47 |
依據先前說的,我們必須要將數列分割為已排序與未排序兩類,為了方便就用|
隔開吧:
1 | NULL | 25 49 22 33 47 |
接下來開始遍歷數列,首先遇到值25
,如前所述,我們要將未排序數插入至已排序數的正確位置,不過目前未排序區段是空的,所以也沒什麼值能跟25
相比,便直接將25放入已排序數列的首位吧:
1 | 25 | 49 22 33 47 |
第二個遇到的是49
,重申最後一次,我們將它插至已排序數列的正確位置。目前已排序的有25
,因為是由小排到大,我們便把49
放到25
的後頭:
1 | 25 49 | 22 33 47 |
第三個遇到的是22
,直觀來排的話,我們會直接把22
放在25
前頭,不過遍歷陣列是循序的,實際上插入排序做的是不斷從22的索引值往前找,直到找到一個數比22小或至陣列盡頭,再將22放於該數後,所以我們會先遇到49
:
1 | 25 49 22 | 33 47 |
因為49
比22
大,那我們應該22
走到前面,這有兩種做法,目前先介紹第一種方法:透過交換兩者的位置,來把22
往前推:
1 | 25 22 49 | 33 47 |
再來遇到25
,顯然25
還是比22
大,再換一次位置:
1 | 22 25 49 | 33 47 |
再往前找,沒了,已經到已排序陣列的尾端,22
也已經放在正確位置:
1 | 22 25 49 | 33 47 |
接下來是33
,首先遇到49
:
1 | 22 25 49 33 | 47 |
因為,49
>33
,讓33
走到前面:
1 | 22 25 33 49 | 47 |
再來33
碰上了25
,它比25
大,所以就不用動了,直接擺在25
後頭:
1 | 22 25 33 49 | 47 |
終於到最後了,47
:
1 | 22 25 33 49 47 | |
49
>47
,往前站:
1 | 22 25 33 47 49 | |
47
>33
,放在33
後頭,排序完成:
1 | 22 25 33 47 49 | |
以上就是插入排序法,演練有點繁瑣,但流程其實能簡要成幾個步驟,假設我們想由小排到大的話:
- 挑選未排序數列的一個值為 Key
- 將 Key 與已排序數 N 比較
- 若 Key >= N,則將 Key 至於 N 後
- 若 Key < N,則將 N 與 Key 位置互換
- 終止條件為找到小於Key的N或遍歷至已排序數列的盡頭
依照這個原則,我們能完成如下的虛擬碼(取自wiki):
1 | //A 為欲排序數列 |
出乎意料的簡單,對吧?附上C語言的簡單實作:
1 | void insertionSort(int arr[],int length){ |
熟悉之後,將key拿掉會更為簡潔:
1 | void insertionSort(int arr[],int length){ |
減少賦值的次數
先前的做法是透過不斷交換來把Key的位置往前推動,但若交換後Key的位置並不正確,其實我們也不一定要把Key塞進去,因為即便塞進了錯誤的位置,再往前查找仍還要做交換,導致要往前N個單位,勢必要進行2N次的賦值。
為了減少賦值的次數,不妨只動Key一次,而這一次恰巧位在已排序陣列的正確位置,實作方法是以一個變數儲存Key的值,看看老例子:
1 | 我們的目標是把22放到正確位置 |
22為Key,我們用變數Key來儲存22
1 | int Key = 22 |
先講結論,同樣的我們會持續往前找,直至陣列盡頭找到一個數小於22,不同點在於我們是對已排序陣列做平移,為Key提供一個合適的插槽,這樣的思維其實更適合插入(insert)的涵意:
我們能透過將前項的值賦與後項達成平移,首先把49後推:
1 | int Key = 22 |
再來把25往後推:
1 | int Key = 22 |
查找到陣列盡頭了,接下來把Key的值放入合適地點:
1 | int Key = 22 |
這麼一來就完成排序了,由於Key一次就放對了,省去了一些不必要的賦值,這也是目前較常用的插入排序方式。附上對應的程式碼:
1 | void insertionSort(int arr[],int length){ |
插入排序的一些特性:
- 插入排序是穩定的,因為我們總是有方向性的比對值
- 插入排序的時間複雜度:
輸入的大小為N,要遍歷整個陣列需要 O(n),從Key往前找也需要 O(n),所以平均而言是 O(n2)。
最佳時間複雜度為 O(n) -> 如已完成排序的情形
平均時間複雜度為 O(n2)
最糟時間複雜度為 O(n2) -> 如完全相反的情形
- 插入排序的空間複雜度:
這要看需不需要輔助空間來存放已排序陣列,上頭兩個方法都是不需要輔助陣列的(in-place),所以為 O(1),如果需要的話就為 O(n)。