1. 程式人生 > 其它 >討厭演算法的程式設計師 1 | 插入排序

討厭演算法的程式設計師 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)