递归行为与归并排序

文章介绍了Master公式用于分析递归算法的时间复杂度,展示了如何用该公式分析归并排序的时间复杂度。通过实例讲解了归并排序的过程,包括分治策略和整合两个有序数组的步骤。此外,文章还探讨了小和问题,提出使用归并排序在O(NlogN)的时间复杂度内解决此问题,避免了O(N^2)的暴力解法,并详细解释了在合并过程中如何计算小和。
摘要由CSDN通过智能技术生成

 master公式  T(N)=a*T(N/b)+O(N^d)        

        T(N):问题的规模是N个数据

        N/b:子过程的规模

        a:调用的次数

        O(N^d)  :除子问题的调用之外,剩余的代码的时间复杂度

使用条件:满足子问题等规模的递归

arr[L,R]范围上求最大值
public class GetMax {
    @Test
    public void test() {
        int[] arr = new int[]{3,4,1};//StackOverflowError栈溢出
        System.out.println(getMax(arr));
    }

    public static int getMax(int[] arr) {
        return process(arr, 0, arr.length - 1);
    }

    //    arr[L,R]范围上求最大值
    public static int process(int[] arr, int L, int R) {
        if (L == R) {  //数组元素只有一个时,直接返回
            return arr[L];
        }
        int mid = L + (R - L) >> 1;//中点

        int leftMax = process(arr, L, mid);
        int rightMax = process(arr, mid + 1, R);
        return Math.max(leftMax, rightMax);
    }
}

归并排序

将整组数组分为左右两组分别排序,两边分别排好序之后整合成一整个有序数组

整合左右两侧有序数组

指针先指向两数组第一个位置,指针所在的数比较大小

较小值拷贝到新开辟的数组中,当前数组指针后移

较大值指针保持不变,等待另一指针移动后与新的值进行比较

直到有一边的指针越界,将另一数组的剩下所有的数拷贝到新数组中

public class MergeSort {
    @Test
    public void test(){
        int[] arr = new int[]{3,1,2};
        MergeSort.mergeSort(arr);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]+" ");
        }
    }
    public static int[] mergeSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return arr;
        }
        MergeSort.process(arr, 0, arr.length - 1);
        return arr;
    }

    public static void process(int[] arr, int L, int R) {
        if (L == R) {
            return;
        }
        int mid = L + (R - L) >> 1;
        MergeSort.process(arr, L, mid);//左数组排序
        MergeSort.process(arr, mid + 1, R);//右数组排序
        MergeSort.merge(arr, L, mid, R);//整合
    }

    //整合
    public static void merge(int[] arr, int L, int mid, int R) {
        int[] help = new int[R - L + 1];//开辟一块等区域的新数组,用来拷贝
        int i = 0;//新数组的指针
        int p1 = L;//左数组的指针
        int p2 = mid + 1;//右数组的指针
        while (p1 <= mid && p2 <= R) {//左右两边的数组都没有越界
            if (arr[p1] <= arr[p2]) {//将较小的数拷贝至help数组
                help[i++] = arr[p1++];
            } else {
                help[i++] = arr[p2++];
            }
        }
        while (p1 > mid && p2 <= mid) {//p1指针首先越界,将p2所在数组剩余的数拷贝到help数组
            help[i++] = arr[p2++];
        }
        while (p2 > R && p1 <= mid) {//p2首先越界,将p1所在数组剩余的数拷贝到help数组
            help[i++] = arr[p1++];
        }

        for (int j = 0; j < help.length; j++) {
            arr[L + j] = help[j];
        }
    }
}

归并排序的时间空间复杂度

小和问题 —— 可以利用归并排序边排序边计算小和,可以将O(n^2)转化为O(NlogN)的复杂度

暴力求解:每经过一个数遍历左边的数,比它本身小就加上  -->   时间复杂度:O(N^2)

左边比n小的数相加  等效于  右边比n大的个数*n

用归并排序求解的时候,左侧的数小才产生小和,右侧的数小不产生小和

合并的时候只看右侧是否有比左侧的数大,有则计算此次合并时所要加的小和值,没有则左侧的指针右移

在两侧指针指向的数相等时,一定是右侧的指针右移

如果左侧的指针右移,左侧指针指向的数大,右侧指针指向的数小,不产生小和,此时就会导致小和漏算

public class SmallSum {
    @Test
    public void test() {
        int[] arr = new int[]{2, 1, 3};//3
        System.out.println(SmallSum.smallSum(arr));
    }

    public static int smallSum(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        return process(arr, 0, arr.length - 1);
    }

    public static int process(int[] arr, int L, int R) {
        if (L == R) {
            return 0;
        }

        int mid = L + (R - L) >> 1;
        return process(arr, L, mid) + process(arr, mid + 1, R) + merge(arr, L, mid, R);
        //左数组的小和+右数组的小和+两数组merge后的小和
    }

    public static int merge(int[] arr, int L, int mid, int R) {
        int help[] = new int[R - L + 1];
        int i = 0, p1 = L, p2 = mid + 1;
        int smallnum = 0;
        while (p1 <= mid && p2 <= R) {//都没越界
            if (arr[p1] < arr[p2]) {
                smallnum = smallnum + arr[p1] * (R - p2 + 1);
                //整合时两数组都有序,当arr[p1]<arr[p2]时,那么arr[p1]一定也小于arr[p2]后的所有数,计算出小和需要加多少次arr[p1]即可
                help[i++] = arr[p1++];
            } else {//如果左右两数组指针指向的数值相同时,一定是右数组的指针右移,拷贝右数组的数并且不产生小和
                //相等时如果拷贝左数组,可能会导致漏算
                //右侧小的时候时候不产生小和
                help[i++] = arr[p2++];
            }
        }
        //小和算完,拷贝剩余数组(不拷贝也可以)
//        while (p2 > R && p1 <= mid) {
//            help[i++] = arr[p1++];
//        }
//        while (p1 > mid && p2 <= R) {
//            help[i++] = arr[p2++];
//        }
        return smallnum;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值