[超全汇总]数据结构与算法(5)归并排序详解及其应用

什么是归并排序?

  • 首先归并排序是10大排序中最常用面试常考的,时间复杂度是nlogn,其次我们学到的二叉树中后序遍历实际上就是归并排序来的。
  • 归并排序,先对左右两部分的进行排好序,在进行合并,二叉树的后序遍历也是先将左右子树遍历好后,在进行操作,比如二叉树的最大深度:先递归遍历对应二叉子树的最大深度在进行叠加
  • 接下来看看归并排序的数组模板
public class mergsort2 {
    public static void main(String[] args) {
        int[] arr = {3, 4, 6, 8, 2, 5, 7, 8, 9, 11, 1, 34, 22};
        //需要定义两个指针,一个是left,mid,right,进行两两划分
        sort(arr, 0, arr.length - 1);
        print(arr);
    }
    static void sort(int[] arr, int left, int right) {
        if (left == right) {//当只有一个的时候就不用一分为二了
            return;
        }
        int mid = left + (right - left) / 2;//默认是在最左边
        //进行左右两边的划分
        sort(arr, left, mid);
        //对右边进行划分
        sort(arr, mid + 1, right);
        merge(arr, left, mid + 1, right);
    }
    public static void merge(int[] arr, int leftPtr, int rightPtr, int rightBound) {
        //1 2 3 5 4 7 9
        // i      j    r---按这个取半分
        int[] temp = new int[rightBound - leftPtr + 1];
        int mid = rightPtr - 1;
        int i = leftPtr;
        int j = rightPtr;
        int k = 0;
        while (i <= mid && j <= rightBound) {
            temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }
        while (i <= mid) {
            temp[k++] = arr[i++];
        }
        while ((j <= rightBound)) {
            temp[k++] = arr[j++];
        }
        for (int l = 0; l < temp.length; l++) {
            arr[leftPtr + l] = temp[l];//因为每一次分temp的大小都只有2,而lef+l就可以保证temp指向的位置也是arr需要的位置,l每次都是从0开始的
        }
    }

    static void print(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }
}
  • 我们来联想下二叉树最大深度的代码
int maxDepth(TreeNode root){
	if(root==null){
	return 0;
	}
	int leftMaxDepth = maxDepth(root.left);
	int rightMaxDpeth = maxDpeth(root.right);
	int res = Math.max(leftMaxDpeth,rightMaxDepth)+1;
	return res;
}
  • 因此,归并排序的过程可以在逻辑上抽象成一颗二叉树,书上每个节点的值可以认为是nums[lo…hi],叶子节点就是数组中的每个元素
    在这里插入图片描述
    接下来我们看几个题,来进一步加深对归并的理解

1.1 912排序数组

在这里插入图片描述

class Solution {
    public int[] sortArray(int[] nums) {
        Merge.sort(nums);
        return nums;
    }
class Merge{
    private static int[]temp;
    public static void sort(int[]nums){
        temp = new int[nums.length];
        sort(nums,0,nums.length-1);
    }
    private static void sort(int[]nums,int lo,int hi){
        if(lo==hi){
            return;
        }
        int mid = lo+(hi-lo)/2;
        sort(nums,lo,mid);
        sort(nums,mid+1,hi);
        merge(nums,lo,mid,hi);
    }
    private static void merge(int[]nums,int lo,int mid,int hi){
        for(int i = lo;i<=hi;i++){
            temp[i] = nums[i];
        }
        //在利用双指针进行合并
        int i = lo, j = mid+1;
        for(int p = lo;p<=hi;p++){
            if(i==mid+1){
                nums[p]=temp[j++];
            }else if(j==hi+1){
                nums[p]=temp[i++];
            }else if(temp[i]>temp[j]){
                nums[p] = temp[j++];
            }else{
                nums[p]= temp[i++];
            }
        }
    }
}
}

1.2 315计算右侧小于当前元素的个数

  • 我们知道归并排序的左右两个是排好序的,我们只需要记录小于当前的数的数量就可以,因此这个怎么记录就是一个难点了,这里需要申明一个类,Pair,构造中声明对应的val和index值,因此当我们遇到小于当前的值得时候可以利用索引下标进行相减
  • 我们只需要在归并排序当中进行改动几行就可
    在这里插入图片描述
class Solution {
    private class Pair{
        int val,id;
        Pair(int val,int id){
            this.val = val;
            this.id = id;
        }
    }
    private Pair[]temp;//辅助数组
    private int[]count;//记录小于自己的数
    public List<Integer> countSmaller(int[] nums) {
        int n = nums.length;
        temp = new Pair[n];
        count = new int[n];
        Pair[]arr = new Pair[n];
        for(int i = 0;i<n;i++){
            arr[i] = new Pair(nums[i],i);
        }
        sort(arr,0,nums.length-1);//归并
        //统计数据
        LinkedList<Integer>res = new LinkedList<>();
        for(int a:count){
            res.add(a);
        }
        return res;
       
    }
    private void sort(Pair[]arr,int lo,int hi){
        if(lo==hi){
            return;
        }
        int mid = lo + (hi-lo)/2;
        sort(arr,lo,mid);
        sort(arr,mid+1,hi);
        merge(arr,lo,mid,hi);
    }
    private void merge(Pair[]arr,int lo,int mid,int hi){
        //将原数组拷贝到对应的temp中
        for(int i = lo;i<=hi;i++){
            temp[i] = arr[i];
        }
        int i = lo,j = mid+1;
        for(int p =lo;p<=hi;p++){
            if(i == mid+1){
                arr[p] = temp[j++];
            }else if(j==hi+1){
                arr[p] = temp[i++];
                count[arr[p].id] += j-mid-1;
            }else if(temp[i].val > temp[j].val){
                arr[p] = temp[j++];
            }else{
                arr[p] = temp[i++];
                count[arr[p].id] += j-mid-1;
            }
        }
    }
}

1.3 翻转对

在这里插入图片描述

class Solution {
    private int[]temp;
    private int count = 0;
    public int reversePairs(int[] nums) {
        sort(nums);
        return count;
    }
    public void sort(int[]nums){
        temp = new int[nums.length];
        sort(nums,0,nums.length-1);
    }
    private void sort(int[]nums,int lo,int hi){
        if(lo==hi){
            return;
        }
        int mid = lo + (hi-lo)/2;
        sort(nums,lo,mid);
        sort(nums,mid+1,hi);
        merge(nums,lo,mid,hi);
    }
    private void merge(int[]nums,int lo,int mid,int hi){
        for(int i = lo;i<=hi;i++){
            temp[i] = nums[i];
        }
        // 进⾏效率优化,维护左闭右开区间 [mid+1, end) 中的元素乘 2 ⼩于 nums[i]
		// 为什么 end 是开区间?因为这样的话可以保证初始区间 [mid+1, mid+1) 是⼀个空区间
        int end = mid+1;
        for(int i = lo;i<=mid;i++){
            while(end<=hi && (long)nums[i]>(long)nums[end]*2){
                end++;
            }
            count += end-(mid+1);
        }
        int i =lo,j=mid+1;
        for(int p = lo;p<=hi;p++){
            if(i==mid+1){
                nums[p] = temp[j++];
            }else if(j==hi+1){
                nums[p] = temp[i++];
            }else if(temp[i]>temp[j]){
                nums[p] = temp[j++];
            }else{
                nums[p] = temp[i++];
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李知恩真爱粉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值