二路归并排序(超详细! 结合剑指offer34题)

二路归并排序

二路归并排序有两个过程:分与和。
在这里插入图片描述如上图:
(a) 把长度为4的数组分解成两个长度为2的子数组;
(b) 把长度为2的数组分解成两个成都为1的子数组;
© 把长度为1的子数组 合并、排序并统计逆序对 ;
(d) 把长度为2的子数组合并、排序,并统计逆序对

Java代码如下:

public class Day0429 {

    @Test
    public void test1(){
        int[] arr = {4, 2, 6, 1, 3, 8, 5, 9};
        mergeSort(arr, 0, arr.length-1);
        showArr(arr);
    }

    //归并排序 参数1:待排数组     参数2:最左边的位置      参数3:最右边的位置
    public static  void mergeSort(int[] arr, int left, int right){
        if(arr == null){
            return;
        }
        if(left < right) {
            //找中间位置进行划分
            int mid = (left+right)/2;
            //对左子序列进行递归归并排序
            mergeSort(arr, left, mid);
            //对右子序列进行递归归并排序
            mergeSort(arr, mid+1, right);
            //“合”。 进行归并
            merge(arr, left, mid, right);
        }
    }

    //将两个有序的序列合并为一个有序的序列,待合并的序列为arr[left,mid] 和 arr[mid+1,right]
    public static void merge(int[] arr, int left, int mid, int right){

        /**
        1.步骤一:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
        2.步骤二:设定两个变量,初始值分别为两个已经排序序列的起始位置;
        3.步骤三:比较以两个变量为下标的元素,选择相对较小的元素放入到合并空间,并使其下标值为下一位置下标;
        4.重复步骤三直到左下标超过右下标;
        5.将另一序列剩下的所有元素直接复制到合并序列尾;
        **/

        int[] tempArr = new int[arr.length];
        int tempIndex = left;
        int leftStart = left;
        int rightStart = mid+1;

        while(leftStart <= mid && rightStart <= right){
            if(arr[leftStart] < arr[rightStart]){
                tempArr[tempIndex++] = arr[leftStart++];
            } else {
                tempArr[tempIndex++] = arr[rightStart++];
            }
        }
        //如果左边剩下了,直接加到数组里面
        while(leftStart <= mid) {
            tempArr[tempIndex++] = arr[leftStart++];
        }
        //如果右边剩下了,直接加到数组里面
        while(rightStart <= right) {
            tempArr[tempIndex++] = arr[rightStart++];
        }
        //将排好序的这部分,赋值到原数组的对应部分
        while(left<=right){
            arr[left] = tempArr[left];
            left++;
        }
    }

    private static void showArr(int[] arr) {
        for(int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }
}

现在来看剑指offer的第34题

题目描述: 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007。

思路1: 我们第一反应便是对每个数字,判断它后面的元素是不是比它小,从而构成逆序。这样的方法是可行的,但是在牛客网提交的时候,提示说超时。

思路2: 我们刚刚学了二路归并排序,其实这道题可以稍微对二路归并算法进行改造。在二路归并的算法的合的时候,是两个排好序的序列进行比较。如下图,我们现在合并两个序列。第一次1和2比较,1放在新的数组第一个位置(新的数组即为合并后的数组)。然后第二次比较,发现4比2大,那么这就是一个逆序。同时,4之后的所有数字都比4大(这个是排好序的序列)。所以此时比2大逆序实际有: mid - i +1个。也就是说当前面的数组的元素和后面数组的元素进行比较较大时,逆序数增加:mid-i+1个。

在这里插入图片描述代码如下:

public class Solution {
    private int cun;
    public void Merge(int[] arr,int start,int end){
        if(start >= end) return;//递归的终止条件
        int mid = (start+end)/2;
        Merge(arr,start,mid);
        Merge(arr,mid+1,end);
        MergeOne(arr,start,mid,end);
    }
    public void MergeOne(int[] arr,int start,int mid,int end){
        //进行排序
        int [] temp = new int[end-start+1];
        int k=0,i=start,j=mid+1;
        while(i<=mid && j<=end){
            if(arr[i]<=arr[j]){//没有逆序
                temp[k++] = arr[i++];
            }else{//有逆序
                temp[k++] = arr[j++];
                cun = (cun+(mid-i+1))% 1000000007;
            }
        }
        //计算没有归并完的情况
        while(i<=mid){
            temp[k++] = arr[i++];
        }
        while(j<=end){
            temp[k++] = arr[j++];
        }
        for(int l=0;l<k;l++){
            arr[start+l] = temp[l] ;
        }
    }
    public int InversePairs(int [] array) {
        Merge(array,0,array.length-1);
        return cun;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值