---
# System prepended metadata

title: 合併排序(Merge Sort)

---

# 合併排序(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$)
:::