算法路漫漫(二) 递归与归并

2021/6/27 22:50:14

本文主要是介绍算法路漫漫(二) 递归与归并,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

master公式

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

当log(b,a) > d  => 复杂度为O ( N^log(b,a) )
当log(b,a) = d  => 复杂度为O ( N^d * logN )
当log(b,a) < d  => 复杂度为O ( N^d ) 

关于master公式详情可以参考: 算法的复杂度与 Master 定理

1. 求一个无序数据的最大值(递归)

递归排序算法的基本思想:将数组分为左右两半部分,分别在左/右半部分别求局部最大值,然后对这两部分的局部最大值求一个全局最大值。递归实际就是系统不断调用栈记录现场相信和还原现场信息的过程,所以所有的递归算法都可以使用非递归实现。时间复杂度O(N)

/**
 * 
 * 递归排序
 * 复杂度T(N) = 2* T(N/2) + O(1) 
 *  a=2 b=2 d=0, log(2,2) > 0
 *  => N
 */
@Slf4j
public class Alg005_SearchMax {


    @Test
    public void test(){
        for(int i=0;i<5000;i++){
            int[] arr  = generateArr();
            log.info("arr:{}",arr);
            int max = getMax(arr, 0, arr.length-1);
            int matchVal = Arrays.stream(arr).max().getAsInt();
            if(max != matchVal){
                arr =null;
                log.info("test failed!");
                return;
            }
            log.info("max:{}",max);
            arr =null;
        }
        log.info("test success!");
    }



    public int getMax(int[] arr, int left, int right){
        if(left == right){
            return arr[left];
        }
        int mid = left+ ((right-left) >> 1);
        int lMax = getMax(arr,left,mid);
        int rMax = getMax(arr, mid+1,right);
        return Math.max(lMax, rMax);
    }
    

    private int[] generateArr(){
        return new Random().ints(-100, 100).distinct().limit(20).toArray();
 
    }
}

 时间复杂度分析:时间复杂度 T(N) = 2T(N/2) + O(1) .O(1)表示Math.max()方法复杂度。根据master公式可知,a = b = 2,d = 0. log(2,2) = 1< 0,所以时间复杂度为O(N).

2. 将一个无序的数组排序 (归并排序)

归并排序基本思路:先左边排好序,然后右边排好序,然后利用外排的方式进行排序 

时间复杂度O(N*logN),额外空间复杂度O(N)

/**
 * 归并排序
 * 复杂度T(N) = 2* T(N/2) + O(N) 
 * a=2 b=2 d=1, log(2,2)=1
 * => NlogN
 */
@Slf4j
public class Alg006_MergeSort {


    @Test
    public void test(){
        for(int i=0;i<5000;i++){
            int[] arr  = generateArr();
            mergeSort(arr, 0, arr.length-1);    
            log.info("arr:{}",arr);
        }
    }


    private void mergeSort(int[] arr, int L, int R){
        if(L == R){
            return;
        }
        int mid = L + ((R-L)>>1);
        // left part merge sort
        mergeSort(arr, L, mid);
        // right part merge sort
        mergeSort(arr,mid+1,R);

        merge(arr, L, mid, R);

    }


    private void merge(int[] arr, int L, int M, int R){
        // additional array
        int[] help = new int[R-L+1];
        int i = 0;
        int p1 = L;
        int p2 = M+1;
        // 
        while(p1<=M && p2<=R){
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        // if the point moved to end in the right part
        while(p1<=M){
            help[i++] = arr[p1++];
        }
        // if the point moved to end in the left part
        while(p2<=R){
            help[i++] = arr[p2++];
        }
        // copy the array to arr
        for(i = 0;i<help.length;i++){
            arr[L+i] = help[i];
        }
    }


    private int[] generateArr(){
        return new Random().ints(-100, 100).distinct().limit(20).toArray();
 
    }
    
}

外排:首先将数组的左右两边排好,准备一个辅助数组arr。然后a指针指向左边第一个,b指针指向右边第一个。如果a比b大,那么a指针不变,移动b指针指向下一个数;如果a比b小,那么b指针不动,移动a指针指向下一个数。谁小谁往辅助数组填,直到a或者b指针移动到最后一个数,然后将剩下的拷贝到辅助数组的后面即可。比如现在a指向3,b指向0,b小,辅助数组第一个填0,然后b移动到下一个,指向1,a不动还是指向3,b比a小,将1填进辅助数组,然后b移动到下一个,指向2,a不动还是指向3,b比a小,将2填进辅助数组,b到尽头,将a剩下的拷贝到辅助数组后面,最后将辅助数组拷贝回原数组,排序完成。

外排的复杂度分析:左右两边T(N) = 2T(N/2),a,b指针移动,拷贝到辅助函数O(N),所以T(N) = 2T(N/2)+O(N),利用master公式可得归并排序复杂度为O(N*logN)。

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

基本思路是在归并排序上的拓展:先左边排好序求小和,然后右边排好序求小和,然后合并左右求小和

/**
 * 归并排序拓展 - 求小和 
 */
@Slf4j
public class Alg007_SmallSort {


    @Test
    public void test(){
        for(int i=0;i<5000;i++){
            // int[] arr = new int[]{1,2,3,4,5};
            // 4*1+3*2+2*3+4*1
            int[] arr  = generateArr();
            int[] arr2 = Arrays.copyOf(arr, arr.length);
            int val = mergeSort(arr, 0, arr.length-1);    
            int compareVal = compareVal(arr2);
            log.info("avl:{}, compareVal:{}", val, compareVal);
        }
    }


    private int mergeSort(int[] arr, int L, int R){
        if(L == R){
            return 0 ;
        }
        int mid = L + ((R-L)>>1);
        // lfet part sort and get small sum
        // right part sort and get small sum
        // merge left and right part and get small sum
       return mergeSort(arr, L, mid) + mergeSort(arr,mid+1,R) + merge(arr, L, mid, R);
    }


    private int merge(int[] arr, int L, int M, int R){
        // additional array
        int[] help = new int[R-L+1];
        int i = 0;
        int p1 = L;
        int p2 = M+1;
        int val = 0;
        // 
        while(p1<=M && p2<=R){
            // if the p1< p2, add the value (r-p2+1)* arr[p1], because alrady sorted both the left and right part 
            // if p1 >= p2, add 0
            val += arr[p1] < arr[p2] ? (R-p2+1)* arr[p1]: 0;
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        // if the point moved to end in the right part
        while(p1<=M){
            help[i++] = arr[p1++];
        }
        // if the point moved to end in the left part
        while(p2<=R){
            help[i++] = arr[p2++];
        }
        // copy the array to arr
        for(i = 0;i<help.length;i++){
            arr[L+i] = help[i];
        }
        return val;
    }


    private int[] generateArr(){
        return new Random().ints(-100, 100).distinct().limit(20).toArray();
 
    }

    private int compareVal(int[] arr){
        int val = 0;
        for(int i = arr.length-1;i>0;i--){
            for(int j = i-1;j>=0 && i>0;j--){
                val += arr[j] < arr[i] ? arr[j] : 0;
            }
        }
        return val;
    }
    
}

 



这篇关于算法路漫漫(二) 递归与归并的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程