



插入排序
借用《算法導論》里的例子,就是我們打牌的時候,每新拿一張牌都會把它按順序插入,這,其實就是插入排序。

齊姐聲明:雖然我們用打牌的例子,但是可不能學胡適先生啊。

對於數組來說怎么做呢?
有一個重要的思想,叫做擋板法,就是用擋板把數組分成兩個區間:
-
擋板左邊:已排序 -
擋板右邊:未排序
那么排序分三步走:
-
最初擋板是在數組的最左邊,index = 0 的位置,也就是保證了已排序區間里一個數都沒有,或者也可以包含一個數啦;
-
核心思想就是:
依次遍歷未排序區間里的元素,在已排序區間里找到正確的位置插入; -
重復這個過程,直到未排序區間為空。
舉個例子:{5, 2, 1, 0}
第一步,擋板最初在這里:

第二步, 把 2 插入已排序區間的正確位置,變成:

重復這個步驟,把 1 排好:

最后把 0 排好:

那代碼也很簡單:
public void insertionSort(int[] input) {
if (input.length <= 1) {
return;
}
for(int i = 1; i < input.length; i++) {
int tmp = input[i];
int j = i - 1;
while(j >= 0 && input[j] > tmp) {
input[j+1] = input[j];
j --;
}
input[j+1] = tmp;
}
}
我們來分析一下這個算法的時空復雜度。
時間復雜度
關於時間復雜度有兩個要點:
-
是描述隨着自變量的增長,所需時間的增長率; -
是漸近線復雜度,就是說 -
不看系數 -
只看最高階項
-
那么我們關心的 worst case 的情況就是:
如果數組是近乎倒序的,每次插入都要在數組的第一個位置插入,那么已排序區間內的所有的元素都要往后移動一位,這一步平均是 O(n),那么重復 n 次就是 O(n^2).
空間復雜度
重點是一個峰值的概念,並不是累計使用的空間。 這里是 O(1) 沒什么好說的。
引入一個概念:sorted in place,也就是原地排序。
原地排序就是指空間復雜度為 O(1) 的算法,因為沒有占用額外的空間,就是原地打轉嘛。
其實 in-place 的思想並不是只在排序算法里有,只不過排序算法是一個最廣為人知的例子罷了。本質上就是一個節省使用空間的思想。
但是對於排序算法,只分析它的時空復雜度是不夠的,還有另外一個重要指標:
穩定性
這個是排序算法的一個重要指標,意思是元素之間的相對順序是否保持了不變。
比如說:{5, 2, 2, 1, 0}
這個數組排序完成后這里面的兩個 2 的相對順序沒有變,那么這個排序就是一個穩定排序。
那有同學可能就想,順序變了又有什么關系呢?
其實,在實際工作中我們排序的對象不會只是一個數字,而是一個個的對象 (object),那么先按照對象的一個性質來排序,再按照另一個性質來排序,那就不希望原來的那個順序被改變了。好像有點抽象,我們舉個例子。
比如在股票交易系統里,有買賣雙方的報價,那是如何匹配的呢?
-
先按照價格排序; -
在相等的價格中,按照出價的 時間順序來排序。
那么一搬來說系統會維持一個按時間排序的價格序列,那么此時只需要用一個具有穩定性的排序算法,再按照價格大小來排序就好了。因為穩定性的排序算法可以保持大小相同的兩個對象仍維持着原來的時間順序。
那么插入排序是否是穩定性的排序呢?答案是肯定的。因為在我們插入新元素的時候是從后往前檢查,並不是像打牌的時候隨便插一個位置不能保證相對順序。
大家可以看下下面的動畫[1] 就非常清楚了~

優化
插入排序其實是有很大的優化空間的,你可以搜一下“希爾排序”。
在剛開始學習的時候,深度固然重要,但因為廣度不夠,如果學的太深可能會很痛苦,一個知識點就無窮無盡的延展,這並不是一個高效的學習方式。
所以如果時間有限,就要做好深度和廣度的平衡:
-
在常用常考的知識點上多花時間精力,追求深度; -
在一些拓展性的知識點上點到為止,先知道有這么回事就行。
保持 open minded 的心態,后期就會有質的提高。
如果你喜歡這篇文章,記得給我點贊留言哦~你們的支持和認可,就是我創作的最大動力,我們下篇文章見!
我是小齊,紐約程序媛,終生學習者,每天晚上 9 點,雲自習室里不見不散!
更多干貨文章見我的 Github: https://github.com/xiaoqi6666/NYCSDE
參考資料
排序動畫: https://visualgo.net/en/sorting