排序算法之归并排序(master公式、归并排序、小和问题)

1、递归算法的master公式

Master公式是用来解决递归问题时间复杂度的公式。

记录主方法的表现形式:
T [n] = aT[n/b] + f (n)(直接记为T [n] = aT[n/b] + O(N^d))

其中 a >= 1 and b > 1 是常量,其表示的意义是n表示问题的规模,a表示递归的次数也就是生成的子问题数,b表示每次递归是原来的1/b之一个规模,f(n)表示分解和合并所要花费的时间之和。

①当d<logb a时,时间复杂度为O(n^(logb a))
②当d=logb a时,时间复杂度为O((n^d)*logn)
③当d>logb a时,时间复杂度为O(n^d)

注意:适用范围为子过程规模相等的情况,否则不适用。

2、归并排序

分析:
①将一个无规律的数组分成两部分,并将这两部分分别排成有序

②定义一个辅助数组(数组和原数组长度相同)和两个指针(分别指向两部分的头)

③指针移动,比较,将较小的一个放入辅助数组,以此进行

④当其中一个数组已经放入完毕,将剩下的一个没有遍历完成的数组放入辅助数组

⑤将辅助数组都给原数组

⑥根据master公式计算时间复杂度:为O(N*logN)

public class Main {
    /**
     * 归并排序:先取一个中间值mid,在定义两个指针,一个指向头L,一个指向尾R
     * 从中间分开,进行左右侧的排序,然后进行归并排序,分别从左右两个排好序列的序列中取
     * 再定义指针p1,p2分别指向两个排好顺序的头和尾,分别是L和mid+1
     * 进行两个数的判断,比较,小的放入事先准备好的数组中,指针下移
     * @param arr
     * @param L
     * @param R
     */
    public static void process(int[] arr,int L,int R){
        if(L == R){
            return;
        }
        int mid = L + ((R-L)>> 1);
        process(arr,L,mid);
        process(arr,mid+1,R);
        merge(arr,L,mid,R);
    }

    private 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;
        //判断p1和p2都越界,利用三元运算符将大的赋值给辅助数组中
        while(p1 <=mid && p2<=r){
            help[i++] = arr[p1] <= arr[p2] ? (help[i++] = arr[p1++]) : (help[i++] = arr[p2++]);
        }
        //当其中一个数组角标越界时候,另一个直接赋值到辅助数组

        //当右边数组全部输入完,将左边数组全部放入辅助数组
        while(p1 <= mid){
            help[i++] = arr[p1++];
        }

        //当左边数组全部输入完,将右边数组全部放入辅助数组
        while(p2 <= r){
            help[i++] = arr[p2++];
        }

        //将辅助数组里面的数,放回原数组
        for(int j=0;j<help.length;j++){
            arr[l+j] = help[j];
        }

    }
}

3、归并排序的小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。

例子
[1,3,4,2,5]
1左边比1小的数:没有
3左边比3小的数:1
4左边比4小的数:1,3
2左边比2小的数:1
5左边比5小的数:1,3,4,2
所以小和为1+1+3+1+1+3+4+2=16

public class SmallSum {
    public static int smallSum(int[] arr){
        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);
    }

    private static int merge(int[] arr, int l, int m, int r) {
        int[] help = new int[r-l+1];
        int i = 0;
        int p1 = l;
        int p2 = m+1;
        int res = 0;
        while (p1 <= m && p2 <= r){
            //小和问题的核心
            /**
             * 代码解释:
             * 利用归并排序求小和问题,就是利用每次比较的时候,都知道右边比左边数大的有几个
             * 比如说此时左边的数 1 2 4 右边的数 2 4 6 8(归并的特点,每次归并都是左右排好序的)
             * 此时p1 = 0;p2 = mid+1;arr[p1] = 1 arr[p2] = 2
             * 所以arr[p1] < arr[p2]
             * 对于arr[p1] ,因为arr[p2] 此时的数大于arr[p1]
             * 又因为arr[p2]此时右边的数都比arr[p2]大,
             * 所以大于arr[p1]的数的个数为(r-p2+1)个
             */
            res += arr[p1] < arr[p2] ? (r-p2+1)*arr[p1] : 0;
            help[i++] = arr[p1]<arr[p2] ? arr[p1++] : arr[p2++];//此时是小于号,表示相等的时候将右边的加入,与归并排序的区别
        }
        while(p1<=m){
            help[i++] = arr[p1++];
        }
        while(p2<=r){
            help[i++] = arr[p2++];
        }
        for(i = 0;i< help.length;i++){
            arr[l+i] = help[i];
        }
        return res;
    }
    public static void main(String[] args){
        int[] arr = new int[]{1,1,3,2};
        System.out.println("小和是:"+SmallSum.smallSum(arr));
        System.out.print("排序后:");
        for(int i=0;i<arr.length;i++){
            System.out.print(arr[i]+",");
        }
    }
}
posted @ 2021-12-26 23:11  刘小呆  阅读(147)  评论(0编辑  收藏  举报