# 合併排序(Merge Sort) 該排序法採用經典的分治策略(Divide and Conquer)將問題分(divide)成一些小的問題然後遞迴求解,而治(conquer)的階段則將分的階段得到的各答案修補在一起,即分而治之。 ## 舉例說明 假設有一陣列 [8, 4, 5, 7, 1, 3, 6, 2],會先將陣列每次拆分成兩組同大小的子陣列,直到每組都只有一個元素再進行排序後合併。 流程如下圖: ![](https://i.imgur.com/KJuTraA.png) 分的部分很好實現,就是每次都拆分成兩半,合的部分需要花點時間想一下。 ## 思路 分的部分應該很好理解,所以著重講一下治(conquer)的部分。 我們需要將兩個已經有序的子序列合併成一個有序序列,比如上圖中的最後一次合併,要將 [4,5,7,8] 和 [1,2,3,6] 兩個已經有序的子序列,合併為最終序列 [1,2,3,4,5,6,7,8]。 將 i 索引指向陣列第一個元素,j 索引指向 mid + 1(右子序列第一個元素),將i 與 j 進行比較,哪個元素比較小就放到 temp 中轉陣列中存放,並且記得將索引後移,一直重複比較直到某一子序列已經遍歷結束。 來看下實現步驟: ![](https://i.imgur.com/w9AIiF9.png) 若某一子序列已經遍歷結束但另一子序列還未遍歷結束,則會將那個子序列的剩下元素全部放進 temp 陣列中,最後將 temp 陣列 copy 回 arr 陣列。 ![](https://i.imgur.com/MooaEPK.png) ## 程式碼實現 使用遞迴方式實現: ```java= package Sort; import java.util.Arrays; public class MergeSort { public static void main(String[] args) { int arr[] = {8, 4, 5, 7, 1, 3, 6, 2}; System.out.println("原序列為:"+Arrays.toString(arr)); int temp[] = new int[arr.length]; mergeSort(arr, 0, arr.length - 1, temp); System.out.println("合併排序後序列為:"+Arrays.toString(arr)); } // divide and conquer public static void mergeSort(int[] arr, int left, int right, int[] temp) { if(left < right) { int mid = (left + right) / 2; // 向左遞迴進行分解 mergeSort(arr, left, mid, temp); // 向右遞迴進行分解 mergeSort(arr, mid + 1, right, temp); // 合併 merge(arr, left, mid, right, temp); } } // merge public static void merge(int[] arr, int left, int mid, int right, int[] temp) { int i = left; // 初始化i, 左邊有序序列的初始索引 int j = mid + 1; // 初始化j, 右邊有序序列的初始索引 int t = 0; // 指向temp陣列的當前索引 // (一) 左右子序列比大小, 將小的放到temp陣列 while(i <= mid && j <= right) { // 左右子序列都還沒遍歷到最後時 if(arr[i] <= arr[j]) { // 若左子序列值小於等於右子序列值,則將之放到 temp 陣列中 temp[t] = arr[i]; t += 1; // temp陣列索引後移 i += 1; // 遍歷下一個 } else { // 右子序列值小於等於左子序列值 temp[t] = arr[j]; t += 1; j += 1; } } // (二) 把有剩餘數據的子序列依次全部放入temp while(i <= mid) { // 左子序列有剩餘的元素 temp[t] = arr[i]; t += 1; i += 1; } while(j <= right) { // 右子序列有剩餘的元素 temp[t] = arr[j]; t += 1; j += 1; } // (三) 將 tmep 陣列的元素 copy 到 arr , 並不是每次都 copy 所有 t = 0; int tempLeft = left; // 用於暫時遍歷 temp 陣列 while(tempLeft <= right) { arr[tempLeft++] = temp[t++]; } } } ``` output ```java= 原序列為:[8, 4, 5, 7, 1, 3, 6, 2] 合併排序後序列為:[1, 2, 3, 4, 5, 6, 7, 8] ``` ## 速度測試 測試八十萬筆資料合併排序需要花多少時間: ```java= public static void main(String[] args) { // int arr[] = {8, 4, 5, 7, 1, 3, 6, 2}; int arr[] = new int[80000]; for(int i = 0; i < 80000; i++) { arr[i] = (int)(Math.random() * 80000); } int temp[] = new int[arr.length]; // System.out.println("原序列為:"+Arrays.toString(arr)); Instant startTime = Instant.now(); mergeSort(arr, 0, arr.length - 1, temp); // System.out.println("合併排序後序列為:"+Arrays.toString(arr)); Instant endTime = Instant.now(); System.out.println("共耗時:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); } ``` output ```java= 共耗時:10 毫秒 共耗時:11 毫秒 共耗時:11 毫秒 共耗時:11 毫秒 ``` 10~11ms 就能對八十萬筆資料進行合併排序。 ## 時間複雜度 假設今天要對一陣列使用合併排序法由小到大排序。 ### 最佳:O(nlogn) ### 平均:O(nlogn) ### 最差:O(nlogn) :::warning T(n) = mergeSort(左子數列) + mergeSort(右子數列) + merge = T(n/2) + T(n/2) + c×n = O($nlog_2n$) :::