插入排序與歸併排序
前言:
排序演算法應該算是演算法入門級的東西了,這裡重新學習演算法,先暫時歸納下個人對兩種演算法的理解。
插入排序:
插入排序可以對應到現實生活中的排隊去停車場停車的場景。假設某家飯店的飯菜十分好吃(流口水),導致來這裡吃飯的人特別多,後面來吃飯準備停車的車排起了長隊。每次只允許一輛車過去找位置,找到位置之後才允許下一輛車進入,依此類推,直到所有的車都停好。轉換成專業的數學模型就是:現有一個無序陣列 A[n],要想對其進行排序。我們先從一個數開始考慮,A0肯定是排好序的。現在假設有A1,那麼這時候應該將A1和A0 進行比較排序。這時候假設再來A2,A2需要與前面排好隊的A0、A1 再進行比較排序。這裡需要注意的是在排序的過程中可能會產生陣列的移動。下面是java程式碼實現的升序排列的整數版本:
1 public static void main(String[] args) { 2 int[] arr = {2, 1, 23, 22, 15, 76, 43, 36}; 3 ascInsertionSort(arr); 4 System.out.println(Arrays.toString(arr)); 5 } 6 7 /** 8 * 升序插入排列 9 * @param arr 傳入的陣列 10 */ 11 private static void ascInsertionSort(int[] arr) { 12 int key = 0; 13 for (int j=1; j<arr.length; j++) { // 因為如果只有一個元素根本不需要排序,所以帶插入的元素的下邊從1開始 14 key = arr[j]; // 用key表示當前用來插入到已排序陣列中的值 15 int i = j-1; 16 for (; i>=0; i--) 17 { 18 // 如果已排完序的陣列的最後一個數比當前待插入的數小,說明不需要移動,直接break,否則需要交換兩個比較值的元素的位置 19 if (arr[i] > arr[i+1]) 20 { 21 arr[i+1] = arr[i]; 22 arr[i] = key; 23 } 24 else 25 { 26 break; 27 } 28 } 29 } 30 }
很容易算出該演算法的耗時主要是兩層for迴圈巢狀導致的,第一層for迴圈迴圈的次數為n次。第二層for迴圈每次執行的最壞的結果是需要把前面排序好的陣列再全部迴圈一次,為:
1 + 2 + 3 + ... + (n-1) = (n2-1)/2。 我們知道對於n的多項式,隨著n的增長,對多項式結果影響結果最大的其實是最高的次數的那個項。所以不難得到該演算法的時間複雜度為:θ(n2)。
選擇排序:
既然講了插入排序,順便講講選擇排序。選擇排序簡而言之就是每次選最帥的,選到最不帥的終結排序。
java實現的程式碼如下:
1 /** 2 * 升序選擇排序 3 * 4 * @param arr 5 */ 6 private static void ascSelectionSort(int[] arr) { 7 int min; 8 int minIndex; 9 for (int i = 0; i < arr.length - 1; i++) { 10 min = arr[i]; // 假設最小的元素是當前就在該位置的元素 11 minIndex = i; 12 for (int j = i + 1; j < arr.length; j++) { 13 if (arr[j] < min) // 如果有元素比最小的元素小,則將該元素的值作為最小元素的值,依次查詢下去,最終找到最小元素所在的下表 14 { 15 min = arr[j]; 16 minIndex = j; 17 } 18 } 19 20 // 迴圈完發現最小元素的位置不是當前在該位置的元素,則交換兩個元素的位置 21 if (minIndex != i) { 22 int temp = arr[i]; 23 arr[i] = arr[minIndex]; 24 arr[minIndex] = temp; 25 } 26 } 27 }
可以發現一個很悲傷的事實,這個演算法的時間複雜度也為:θ(n2)。
歸併排序:
歸併排序的思路應該是源自於遞迴。也就是大事化小,小事化了。既然是個大的陣列,我就先分成兩個。兩個陣列還是有點大吧,那我再每個分成兩個。依此下去直到最後沒一組中只剩下一個元素,這時候排序應該是很簡單的事了。而每兩個小組排序完了之後組成大組,由於每個大組都是排序好的,這時候合併大組就簡單多了。用偽公式表示為:一個大組排序的時間 = 兩個小組分別排序的時間 + 兩個小組合並的時間。合併的原理也很簡單,就相當於兩份撲克牌,正著放的,從上到下是從小到大的順序。首先從兩份撲克中各取出一個牌,將較小的那個牌倒著放到另外個地方。再從較小的撲克牌出現的那份牌裡面拿出最上面的,與剛才剩下的牌比大小,同樣的道理,小的牌繼續倒著放到剛剛選出來的那個牌的上面。依次類推,直到有一份牌被拿完。最後將剩下的那份牌倒過來倒著放到選出的牌堆上面,就完成了排序。
java實現的程式碼如下:
1 /**
2 * 升序歸併排列
3 *
4 * @param arr
5 */
6 private static void ascMergeSort(int[] arr) {
7 int startIndex = 0;
8 int endIndex = arr.length - 1;
9 divideConquer(arr, startIndex, endIndex);
10
11 }
12
13 private static void divideConquer(int[] arr, int startIndex, int endIndex) {
14 int midIndex = (startIndex + endIndex) / 2;
15 if (midIndex > startIndex) {
16 divideConquer(arr, startIndex, midIndex); // 排序前一部分
17 divideConquer(arr, midIndex + 1, endIndex); // 排序後一部分
18 }
19 mergeSortedArr(arr, startIndex, midIndex, endIndex); // 合併排序後的兩個陣列
20 }
21
22 private static void mergeSortedArr(int[] arr, int startIndex, int midIndex,
23 int endIndex) {
24 int[] newArr = new int[endIndex + 1];
25 int i = startIndex;
26 int j = midIndex + 1;
27 int k = startIndex;
28 while (i <= midIndex && j <= endIndex) {
29 // 將小的放入當前位置,並且下一個比較大小的從出現小的那一組更新
30 if (arr[i] < arr[j]) {
31 newArr[k++] = arr[i++];
32 } else {
33 newArr[k++] = arr[j++];
34 }
35 }
36
37 // 需要將還剩牌的那一組元素加到排序好的陣列後面
38 while (i <= midIndex) {
39 newArr[k++] = arr[i++];
40 }
41 while (j <= endIndex) {
42 newArr[k++] = arr[j++];
43 }
44
45 // 將新陣列的值複製到老陣列
46 for (i = startIndex; i <= endIndex; i++) {
47 arr[i] = newArr[i];
48 }
49 }
該演算法具體到每一層的時間是n的一定倍數,然後從最頂層到最下面一層可分的層次為logn.所以該演算法的時間複雜度為: θ(nlogn).
氣泡排序:
因為氣泡排序不是今天的主角,所以這裡不再將其程式碼貼出來。只是說說氣泡排序的原理:其實和選擇排序有些類似,也是最小的或者最大的冒出來,不同之處在於在冒泡的過程中會發生置換,每次比較只要比較相鄰的兩個數即可。其時間複雜度其實和選擇排序一樣,這裡直接跳過。
OK!演算法入門之簡單的排序演算法到此完結!