一、归并排序的理解
本质而言可以看作二叉树遍历的一种应用。

二、merge操作

merge方法用于将已经排好序的左右两边进行整理和合并。

假设要被排序的数组被分成了很多个小块, 在进行当前递归步骤之前,每一个小块上的元素已经是有序的了,需要通过merge操作将两个已经排号序的小块整理为一整块有序部分。
这里的merge操作在非递归实现和递归实现中是通用的。

// 
    public static void merge(int[] arr, int L, int M, int R){
        int[] help = new int[R - L + 1]; // 设置一个长度为L到R元素个数的辅助数组help
        int i = 0; // 为help使用所准备的指针
        int p1 = L; // 设置一个左指针从左往右移动
        int p2 = M + 1; // 设置一个右指针从右往左移动
        while (p1 <= M && p2 <= R){ // 当左右指针都没有越界时
            // help中放入p1或p2指针所对应的元素中教小的那个,i以及对应指针p1或p2移动一位
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++]:arr[p2++];
        }
        // 上面的while循环终止的条件是,要么p1越界,要么p2越界
        // p1,p2不可能都越界,所以下面两个while循环只会有一个执行
        // 然后将没越界的那个要走的剩余部分放入help中
        while (p1 <= M){
            help[i++] = arr[p1++];
        }
        while (p2 <= R){
            help[i++] = arr[p2++];
        }
        // 然后将help数组中的内容重新刷入arr中
        for (i = 0; i < help.length; i++){
            arr[L + i] = help[i];
        }
    }

三、递归实现

public class Code01_MergeSort {
    // 递归方法实现
    public static void mergeSort1(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        process(arr, 0, arr.length - 1);
    }

    // 递归函数的定义:让arr在[L..R]范围上, 变成有序的
    public static void process(int[] arr, int L, int R) {
        if (L == R){ // base case: 当范围上只有一个数时停止划分
            return;
        }
        int mid = L + ((R - L) >> 1); // 如果范围上不止一个数, 先选出中点
        process(arr, L, mid);
        process(arr, mid+1, R);
        merge(arr, L, mid, R);
    }
}

四、非递归(迭代)实现

用迭代的方法进行归并排序的话,可以理解为,自初始的有序长度1开始,不断对已经有序的左组和长度位置,未经排序的右组进行合并,在合并的过程中左右组会一起变得有序,每完成一次进行合并的区域长度倍增。

public static void mergeSort2(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        int N = arr.length;
        // 如果设置组长度K,应从2开始
        int mergeSize = 1; // 当前有序的,左组长度,从1开始
        while(mergeSize < N){ // log N
            int L = 0; // L用于枚举第N个左组的位置从哪里开始,一次归并完成会扩大mergeSize回到下标0位置重新开始新一轮归并
            // 0 - ..
            while(L < N){
                // L .. M 当前的左组,其长度为mergeSize
                int M = L + mergeSize - 1; // M的位置可以直接算出
                if (M >= N){ // 当前左组的右端位置已经到达arr末端,可以不用再排序了
                    break;
                }
                // L..M M+1..R(mergeSize)
                int R = Math.min(M + mergeSize, N - 1);// 右组右端的位置取决于剩余元素数量够不够和左组一样长
                merge(arr, L, M, R);
                L = R + 1;// 一次左右组归并完成,将L移动至R+1位置
            }
            // 加上下面这一句是为了防止溢出:可能会有未知的错误, 例如超过Int类型最大值(21亿左右)
            if (mergeSize > N / 2){ // 判断2 * mergeSize是否大于arr长度
                break;
            }
            mergeSize <<= 1; //每次完成一组排序后如果组长度仍然不比arr长度大,mergeSize*2
        }
    }

五、总结

递归操作的时间复杂度公式:

若递归操作符合:java 中merge 函数 java merge into_java 中merge 函数

  1. java 中merge 函数 java merge into_时间复杂度_02 ⇒ 时间复杂度为:java 中merge 函数 java merge into_算法_03
  2. java 中merge 函数 java merge into_算法_04 ⇒ 时间复杂度为:java 中merge 函数 java merge into_算法_05
  3. java 中merge 函数 java merge into_java_06 ⇒ 时间复杂度为:java 中merge 函数 java merge into_数据结构_07

d 表示除了递归调用之外剩余所有行为的时间复杂度。

计算时间复杂度: java 中merge 函数 java merge into_数据结构_08

尾项为merge的时间复杂度,merge的时间复杂度为java 中merge 函数 java merge into_算法_09,所以对于递归的归并排序而言,d=1。
两个2的来源是“二分等规模的递归操作”。

java 中merge 函数 java merge into_java 中merge 函数_10 所以时间复杂度为:java 中merge 函数 java merge into_数据结构_11
归并排序的时间复杂度为java 中merge 函数 java merge into_数据结构_11

归并排序相较于冒泡排序、选择排序和插入排序的时间复杂度java 中merge 函数 java merge into_java 中merge 函数_13,其优势在于把比较的结果作为有效信息固定了下来 ,而上面的三种排序,在一次遍历的比较操作中只有一次是有效的。