插入排序
借用《演算法導論》裡的例子,就是我們打牌的時候,每新拿一張牌都會把它按順序插入,這,其實就是插入排序。
齊姐宣告:雖然我們用打牌的例子,但是可不能學胡適先生啊。
對於陣列來說怎麼做呢?
有一個重要的思想,叫做擋板法,就是用擋板把陣列分成兩個區間:
- 擋板左邊:已排序
- 擋板右邊:未排序
那麼排序分三步走:
- 最初擋板是在陣列的最左邊,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),那麼先按照物件的一個性質來排序,再按照另一個性質來排序,那就不希望原來的那個順序被改變了。好像有點抽象,我們舉個例子。
比如在股票交易系統裡,有買賣雙方的報價,那是如何匹配的呢?
- 先按照價格排序;
- 在相等的價格中,按照出價的時間順序來排序。
那麼一搬來說系統會維持一個按時間排序的價格序列,那麼此時只需要用一個具有穩定性的排序演算法,再按照價格大小來排序就好了。因為穩定性的排序演算法可以保持大小相同的兩個物件仍維持著原來的時間順序。
那麼插入排序是否是穩定性的排序呢?答案是肯定的。因為在我們插入新元素的時候是從後往前檢查,並不是像打牌的時候隨便插一個位置不能保證相對順序。
大家可以看下下面的動畫 就非常清楚了~
優化
插入排序其實是有很大的優化空間的,你可以搜一下“希爾排序”。
在剛開始學習的時候,深度固然重要,但因為廣度不夠,如果學的太深可能會很痛苦,一個知識點就無窮無盡的延展,這並不是一個高效的學習方式。
所以如果時間有限,就要做好深度和廣度的平衡:
- 在常用常考的知識點上多花時間精力,追求深度;
- 在一些擴充性的知識點上點到為止,先知道有這麼回事就行。
保持 open minded 的心態,後期就會有質的提高。
如果你喜歡這篇文章,記得給我點贊留言哦~你們的支援和認可,就是我創作的最大動力,我們下篇文章見!
我是小齊,紐約程式媛,終生學習者,每天晚上 9 點,雲自習室裡不見不散!
更多幹貨文章見我的 Github: https://github.com/xiaoqi6666...