归并排序的原理:将数据一分为2,一直分到不可再分,让左边的数据有序,右边的数据也有序,然后两边合并

public class Demo {
    public static void main(String[] args) {
        int[] nums={98,45,23,31,2,66,88};
        //一般归并是写在方法中的,所以要先判断数组中的元素个数,如果是空或1,那么不必排序
        if(nums.length<=1){
            return;
        }

        //开始下标
        int start = 0;

        //结束下标
        int end = nums.length-1;

        //将数组start与end之间的数据进行递归
        process(nums,start,end);

        //输出打印数组
        print(nums);
    }

    public static void print(int[] nums) {
        for(int i = 0;i<nums.length;i++){
            System.out.print(nums[i]+"\t");
        }
    }

    /**
     * 归并
     * @param nums :要进行递归的数组
     * @param start :传入要进行递归的数组的开始下标
     * @param end :传入要进行递归的数组的结束下标
     * */
    public static void process(int[] nums, int start, int end) {
        //如果开始下标和结束下标一致,那么就返回
        if(start == end){
            return;
        }

        //中点
        int middle = start + ((end - start)>>1);
        //左边有序
        process(nums,start,middle);
        //右边有序
        process(nums,middle+1,end);
        //合并
        merge(nums,start,middle,end);

        /*
        * 将一个数组先一分为2进行递归,但因为刚开始一分为2的时候开始下标不等于结束下标,所以一直递归到单数
        * 分析:
        *   设数组元素为  a   b   c   d   e
        *   主函数调用递归:    a,b,c           d,e(这个时候左边已经有序,该执行右边有序的递归函数,执行过程如左边一致)
        *   左边有序:       a,b    c(merge(nums,0,1,2),merge完成之后,结束当前递归,返回到上一级)
        *       左边有序: a     b(merge(nums,0,0,1),merge完成之后,结束当前递归,返回到上一级)
        *       当元素个数为单个的时候,结束当前递归,此时进行merge函数的调用
        * */
    }

    /**
     * 合并并排序
     * @param nums :要进行合并的数组
     * @param start :要进行合并的元素的范围的开始下标
     * @param middle :要进行合并的元素的范围的中间下标
     * @param end :要进行合并的元素的范围的结束下标
     * */
    public static void merge(int[] nums, int start, int middle, int end) {
        //定义一个长度为end+1的数组,因为end是结束下标,所以数组长度为end+1
        int[] newArray = new int[end+1];

        /**
         * 因为要进行merge函数的第一个范围一定是【0-1】
         * 每次排序是要把开始下标-结束下标之间的数排好
         * 所以i一定要等于start
         * */
        int i = start;

        //左边部分的指针
        /**
         * 因为是要比较左边部分和右边部分的大小
         * 若左边指针比右边指针所指的数大或小,左边指针或右边指针进行++操作,
         * 若左边指针指向0,每次归并的时候都是拿0下标的数和右边指针进行比较,会出现真正需要排序比较的部分没有和右边指针进行排序
         * 所以左边指针不能指向0
         * */
        int tempLeft = start;

        //右边部分的指针
        int tempRight = middle+1;

        //从小到大归并
        while(tempLeft<=middle && tempRight<=end){
            newArray[i++] = nums[tempLeft] <= nums[tempRight] ? nums[tempLeft++]:nums[tempRight++];
        }

        //若右边部分已经排好序,左边还有剩余,则将剩下的元素移下来
        while(tempLeft<=middle){
            newArray[i++]=nums[tempLeft++];
        }

        while(tempRight<=end){
            newArray[i++]=nums[tempRight++];

        }

        //哪部分进行了排序将哪部分正确的顺序赋值给原数组
        for(i=start;i<newArray.length;i++){
            nums[i]=newArray[i];
        }
    }
}

归并排序的时间复杂度分析:
忽略打印输出,归并排序只需要一个递归函数和一个merge合并函数,递归函数是自己调自己,merge函数只进行了一个比较并赋值的操作
归并函数的时间复杂度是:T(N)=2*T(N/2)+O(1)+O(N)

  • 2*T(N/2):递归函数里面的两个自己调自己部分
  • 1:递归函数自己调自己上面的判断和定义中点变量所耗费时间复杂度
  • N:merge函数所耗费时间复杂度,因为merge函数就定义了两个指针,两个指针都是从左到右进行比较并赋值给原数组,所以是2个O(N),忽略系数
    符合递归函数的公式,所以归并函数的时间复杂度是O(N*logN)