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

导读:本篇文章讲解 [超全汇总]数据结构与算法(5)归并排序详解及其应用,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

什么是归并排序?

  • 首先归并排序是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++];
            }
        }
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/101994.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!