討厭演算法的程式設計師 1 | 插入排序
什麼是演算法
在說插入排序之前,我們瞭解下《演算法導論》對演算法的從兩種不同角度的定義。
一般性解釋:
演算法是定義良好的計算過程,它取一個或一組值作為輸入,併產生出一個或一組值作為輸出。
基於應用的解釋:
演算法是一種工具,用來解決一個具有良好規格說明的計算問題。該問題的描述可以用通用的語言,來規定所需的輸入/輸出關係。與之對應的演算法則描述了一個特定的計算過程,用於實現這一輸入/輸出關係。
後一種解釋在告訴我們,我們不必對於每個問題都去重新設計、證明和實現演算法,而是有能力將實際問題轉換成已知演算法問題,然後選取合適的解法。而這種能力就是學習演算法的目的所在。這要求我們不僅要積累演算法知識,還要在更高的抽象層次上理解演算法的方法論。
排序問題的形式定義
排序問題是演算法要解決的一個基本問題。它的形式化定義如下:
輸入:n個數的一個序列[a1, a2, ..., an]。
輸出:輸出序列的一個排列[a1', a2', ..., an'], 滿足a1' ≤ a2' ≤ ... ≤ an'。
插入排序演算法
插入排序演算法,對於少量元素的排序問題,是一個有效的演算法。
經典應用
撲克
即便是玩過撲克牌的小孩子,其實都對插入排序演算法瞭然於胸。
- 摸牌之前,所有將會被你摸到的牌,此時是一種亂序的狀態。
- 你開始摸牌,並將新摸到的牌插入到合適的位置,以保證你手中的牌總是有序的。
- 直到摸到最後一張,將其插入到合適的位置,此時你手中所有的牌就已經排好序了。
演算法的抽象表達
想讓計算機聽懂上述的演算法,需要將其進行翻譯。
INSERTION-SORT(A)
1 for j = 2 to A.length
2 key = A[j]
3 // Insert A[j] into the sorted sequence A[1..j-1].
4 i = j - 1
5 while i > 0 and A[i] > key
6 A[i + 1] = A[i]
7 i = i - 1
8 A[i + 1] = key
原著中的偽碼無可挑剔,就沿用了。做一些說明:
- 演算法名稱INSERT-SORT;
- A是一個長度為n的要排序的陣列,用A.length表示n;
- 把待排序陣列A邏輯上分為兩個陣列,排好序的(手中的牌),未排序的(桌上待摸的牌);
- 排好序的陣列起始只有一個元素,就是原陣列A的第一個元素(摸出的第一張牌無需排序),迴圈變數用i表示;
- 未排序的陣列起始,從原陣列A的第二個元素一直到最後一個元素,所以外層的遍歷是“2 to A.length”,迴圈變數用j表示;
- 外層遍歷過程中,每個當前元素A[j]都暫存至key變數中;
- key要加入排序好的陣列,會從該排序好陣列的最末i(j-1)開始進行比較,如果A[i]大於key,A[i]移動到A[i+1],i自減,繼續與key比較,如果此時i ≤ 0 或者 A[i] ≤ key,迴圈條件不成立跳出,將key存入A[i+1]。
演算法工作例子
插入演算法如何工作
說明:
- (a)~(e)是迴圈迭代,(f)是最終排好的陣列;
- 陣列上方是陣列的下標;
- 黑色塊表示每次外層迭代,待插入左側陣列的數A[j];
- 灰色塊表示參與和A[j]比較的數;
- 黃色箭頭是偽碼第6行表達的移動;
- 藍色大箭頭是偽碼第8行表達的移動;
Java實現
public class InsertionSort {
public static void sortInASC(int[] numbers){
for(int j = 1; j < numbers.length; j++){
int key = numbers[j];
int i = j - 1;
while(i >= 0 && numbers[i] > key){
numbers[i + 1] = numbers[i];
i = i - 1;
}
numbers[i + 1] = key;
}
}
}
InsertSort.java下載 (https://github.com/EthanYuan/algorithm/tree/master/src/algorithm)