18-归并排序

1. 基本思想

  • 归并排序(MERGE-SORT) 是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer) 策略
  • "归并"思想
    • 基于归并这个简单的操作,也就是说如果想要将一个数组排序,可以先(递归地) 将它分成两半分别排序,然后将结果归并起来,即将两个有序的数组归并成一个更大的有序数组。根据这个操作所发明了一种递归排序算法 → 归并排序。
  • "分治"策略
    • 分治法将问题成一些小的问题,然后递归求解
    • 的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之
  • 优缺点
    • 优点:它能够保证将任意长度为 N 的数组排序所需时间和 Nlog2N 成正比
    • 缺点:它所需的额外空间和 N 成正比

2. 标配

  • 平均时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(nlogn)
  • 最好时间复杂度:O(nlogn)
  • 空间复杂度:O(n)

3. 举例示意

3.1 分治全过程

3.2 最后一次merge

要将 [4, 5, 7, 8] 和 [1, 2, 3, 6] 两个已经有序的子序列,合并为最终序列 [1, 2, 3, 4, 5, 6, 7, 8]。

4. 代码实现

public class MergeSort {
    public static void main(String[] args) {
        int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
        int[] temp = new int[arr.length];
        mergeSort(arr, 0, arr.length-1, temp);
        System.out.println(Arrays.toString(arr));
    }

    public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            // int mid = (left + right) / 2;
            // 对其优化, 万一 left+right 超出表示范围了呢?
            int mid = left + (right-left) >> 1;
            // 向左递归继续分
            mergeSort(arr, left, mid, temp);
            // 向右递归继续分
            mergeSort(arr, mid + 1, right, temp);
            // 递归 → 从栈顶开始合并
            merge(arr, left, mid, right, temp);
        }
    }

    /**
     * 对左右序列进行合并
     * @param arr 待排(子)数组
     * @param left 待排数组首元素索引
     * @param mid 中间索引, 用来分割左右两边
     * @param right 待排数组尾元素索引
     * @param temp 临时存储合并后的有序序列
     */
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;     // 左半边有序序列的首元素索引
        int j = mid + 1;  // 右半边有序序列的首元素索引
        int t = 0;        // temp[] 的当前索引

        // 1. 把左右两边(有序)的数据按照规则填充到temp中
        while (i <= mid && j <= right) {
            // 可得有'=', 不然成不稳定的了!
            if (arr[i] <= arr[j]) temp[t++] = arr[i++];
            else temp[t++] = arr[j++];
        } // 直到左右两边的有序序列有一边处理完毕为止

        // 用三目优化 step1 的 if-else
        // temp[t++] = arr[i]<=arr[j] ? arr[i++] : arr[j++];

        // 2. 把未处理完的一边的剩余数据依次全部填充到 temp 去
        // a. 左边的有序序列还有剩余元素
        while (i <= mid) temp[t++] = arr[i++];
        // b. 右边的有序序列还有剩余元素
        while (j <= right) temp[t++] = arr[j++];

        /*
         * 3. 把 temp[] 的元素拷贝到 arr (并不是每一次都拷贝所有元素)
         *  (1) left = 0, right = 1
         *  (2) left = 2, right = 3
         *  (3) left = 0, right = 3
         *  (4) left = 4, right = 5
         *  (5) left = 6, right = 7
         *  (6) left = 4, right = 7
         *  (7) left = 0, right = 7
         */

        // 一定要记得 copy!
        t = 0;
        while (left <= right) arr[left++] = temp[t++];
    }
}

5. Tips

  • arr[i] <= arr[j],别把 '=' 忘了,不然就成不稳定了!
  • merge 完一定要记得把 temp 拷贝回 arr!
  • merge 里的 t = 0 / left 都行 (往回拷贝的时候记得统一!),具体区别只是在于 temp 数组是怎么被使用的~
原文地址:https://www.cnblogs.com/liujiaqi1101/p/12327756.html