你一定可以理解的归并排序

归并排序

  1. 时间复杂度O(nlogn)

  2. 空间复杂度O(n)

  3. 稳定

  4. 思路

    • 使用递归的方式不断切分子区间,当区间长度小于一定值后,对小区间采取插入排序,插入排序在小区间,基本有序的情况下的性能十分优异
  • 写代码的时候还是有很多坑的,思路要时刻保持清醒,特别是在确定数组索引的时候`,一定要清楚地知道想干神魔

    //基础版
    public static void main(String[] args) {
            int [] nums={8,6,5,9,88,4,4,2,6,5,7,1};
            int left=0;
            int right=nums.length-1;
             mergeSort(nums,left,right);
            System.out.println(Arrays.toString(nums));
        }
    
        private static void mergeSort(int[] nums, int left, int right) {
            //当left=right时,子区间只存在一个元素,递归终止
            //当left>right,子区间越界,递归终止
            if(left>=right){
                return ;
            }
            //防止大整形溢出
            int mid=left+(right-left)/2;
            //将数组一分为而,递归地进行下去
    
            mergeSort(nums,left,mid);
            mergeSort(nums,mid+1,right);
            //来到这一个就是划分的两个子数组已经有序,现在要做的就是将两个有序的数组合并成一个有序的数组
            mergeTwoSortedArray(nums,left,mid,right);
    
        }
    
        private static void mergeTwoSortedArray(int[] nums, int left,int mid, int right) {
            //毫无疑问,需要额外的空间来完成这一合并过程
            int len=right-left+1;
            int [] temp=new int[len];
    
            //将元素拷贝到新数组中保存元素,后续直接对原数组元素进行操作
            for (int i = 0; i < len; i++) {
                temp[i]=nums[left+i];
            }
    
            //需要将在nums的索引映射到从零开始,减去left即可
            int l=0;
            int r=mid-left+1;
    
            for (int i = 0; i < len; i++) {
                //这里可能会懵逼
                //当其中一个子区间的元素用尽了.那么从另一个区间来拿就好了,这是前两个if
                //剩下就需要比较一下,拿小的那一个元素就好
                if(l>mid-left){
                    nums[i+left]=temp[r];
                    r++;
                }else if(r>len-1){
                    nums[i+left]=temp[l];
                    l++;
                }else if(temp[l]>temp[r]){
                    nums[i+left]=temp[r];
                    r++;
                }else {
                    nums[i+left]=temp[l];
                    l++;
                }
            }
    
        }
    
    
   
优化点
   
   1. 每次合并时都要开辟新的数组,影响性能
   2. 如果要合并的区间已经有序(前一个区间的最大值小于后一个区间的最小值)就不需要对其进行合并
   3. 当区间的长度小于某个值时,对其进行插入排序


//优化版
    private static final int INSERTION_SORT_THRESHOLD = 6;

    public static void main(String[] args) {
        int[] nums = {8, 6, 5, 9, 88, 4, 4, 2, 6, 5, 7, 1};
        int left = 0;
        int right = nums.length - 1;

        //使用同一份临时数组,避免每次给次归并创建新数组的开销
        int[] temp = new int[nums.length];


        mergeSort(nums, left, right, temp);
        System.out.println(Arrays.toString(nums));
    }

    private static void mergeSort(int[] nums, int left, int right, int[] temp) {
        //当区间长度小于某个值以后采用插入排序
        if (right - left <= INSERTION_SORT_THRESHOLD) {
            insertionSort(nums, left, right);
            return;
        }


        //避免整形溢出
        int mid = left + (right - left) / 2;
        mergeSort(nums, left, mid, temp);
        mergeSort(nums, mid + 1, right, temp);

        //如果数组已经有序,就不用合并了
        if (nums[mid] <= nums[mid + 1]) {
            return;
        }
        mergeTwoSortedArray(nums, left, mid, right, temp);
    }

    private static void mergeTwoSortedArray(int[] nums, int left, int mid, int right, int[] temp) {
       	//其实这里我开始犯傻想把这个动作,放到程序开头,直接复制一个不就好了嘛,免得每次复制一次多麻烦
        //你觉得我想的有道理吗,你可以帮帮我嘛(老猿警告)
        //答案当然不行,你必须复制的是两个排好序的子区间,而不是开始的无序数组
        System.arraycopy(nums, left, temp, left, right - left + 1);
        int l = left;
        int r = mid + 1;
        for (int i = left; i <= right; i++) {
            if (l == mid + 1) {
                nums[i] = temp[r];
                r++;
            } else if (r == right + 1) {
                nums[i] = temp[l];
                l++;
            } else if (temp[l] <= temp[r]) {//这里的等号关乎稳定性,不要忘记了 优先往数组放前面的元素,保持稳定性
                nums[i] = temp[l];
                l++;
            } else {
                nums[i] = temp[r];
                r++;
            }
        }

    }

    private static void insertionSort(int[] nums, int left, int right) {

        for (int i = left + 1; i <= right; i++) {
            int temp = nums[i];
            int j = i;
            while (j > left && nums[j - 1] > temp) {
                nums[j] = nums[j - 1];
                j--;
            }
            nums[j] = temp;
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值