1.2递归

1.2.1线性递归

实例-数组求和

int sum(int A[],int n){
    
    
    if(1>n){
    
    //平凡情况,递归基
        return 0;//直接计算(非递归式子)
    }
    else{
    
    //一般情况
        return sum(A,n-1)+A[n-1];//递归前n-1之项和,再累计计算n-1项
    }
}//O(1)*递归深度 =O(1)*(n+1)=O(n)

线性递归

sum算法可能朝着更深一层进行自我调用,且每一递归实例对自身的调用至多一次。于是,每一层次上至多只有一个实例。且他们构成一个线性的次序关系。此类递归模式因而称作“线性递归"(linear recursion),它也是递归的最基本模式

减而治之

递归每深入一层,待求解问题的规模都会缩减一个常数,直至蜕化为平凡的小(简单)问题。

1.2.1递归分析

递归跟踪

  1. 算法的每一递归实例都表示为一个方框,其中注明了该实例调用的参数
  2. 若实例M想要调用实例N,则在M和N对应的方框之间添加一条有向连线

递推方程

通过对递归模式的数学归纳,导出复杂度定界函数的递推方程(组)及其边界条件,从而将复杂度的分析,转化成递归方程(组)的求解。

实例-计算int sum(int A[],int n)函数的空间复杂度

设采用该算法对长度n的数组统计总和。所需空间量为S(n),于是乎可得递推方程如下:

S(1)=O(1);
S(n)=S(n-1)+O(1);
两式联合求解可得
S(n)=O(n);

1.2.3递归模式

  1. 多递归基-递归基有多个
  2. 实现递归-确保分成的子问题与原问题含义相同
  3. 多向递归-递归调用有多个可供选择的分支(例如分段函数)

1.2.4递归消除

空间成本

递归相比同一算法的迭代版,递归版往往需要更多的空间,并进而影响实际的运行速度。另外,就操作系统而言,为实现递归调用需要花费大量的时间以创建、维护和消除各递归实例,也会使得计算的负担雪上加霜。也因此,为了优化运行速度和存储空间,往往采用非递归的等价算法

尾递归及其消除

在线性递归算法中,若递归调用在递归实例中恰好以最后一步操作的形式出现,则称作尾递归。例如如下代码中,算法的最后一步操作是对去除了首、末元素之后的总长缩减两个单元的子数组进行递归倒置,即属于典型的尾递归

void reverse(int *A,int lo,int hi){
    
    
    if(lo<hi){
    
    
        swap(A[lo],A[hi]);//交换A[lo]和A[hi]
        reverse(A,li+1,hi-1);//递归倒置A(lo,hi)
    }//else包含两种递归基
}//O(li-hi+1)

1.2.5二分递归

分而治之

将大问题分解为若干个小问题,再通过递归机制分别求解。这种分解持续进行,直到子问题规模缩减至平凡情况。

数组求和-分而治之

int sum(int A[],int lo, int hi){
    
    
    if(lo==hi){
    
    
        return A[lo];
    }
    else{
    
    //否则,一般情况下lo<hi,则
        int mi=(lo+hi)>>1;//左移一位,以居中单位为界,将原区一份为二
        return sum(A,lo,mi)+sum(A,mi+1,hi);//递归对各子数组求和,然后合计
    }
}//O(hi-li+1),线性正比于区间的长度

实例

Fibonacci 数:二分递归

递归形式
f i b ( n ) = { n , n < = 1 f i b ( n − 1 ) + f i b ( n − 2 ) , n > = 2 fib(n)=\begin{cases} n,n<=1\\ fib(n-1)+fib(n-2), n>=2\end{cases} fib(n)={ nn<=1fib(n1)+fib(n2)n>=2

_int64 fib(int n){
    
    //计算Fibonacci数列的第n项(二分递归版)
    return (2>n)?
        (_int 64) n//若能到达递归基,直接取指
        : fib (n-1)+ fib(n-2);//否则递归计算前两项,其和即为正解
}

优化策略-类似记忆化搜索

借助一定量的辅助空间,在各子问题求解之后,及时记录下其对应的解答

例如下列线性递归版fib数列

_int64 fib(int n,_int64 &prev ){//计算Fibonacci数列的第n项(线性递归版): 入口形式()
    if(0==n){//若能到达递归基
        prev=1;
        return 0;//直接取值fib(-1)=1,fib(0)=0;
    }
    else{
        _int64 prevPrev;
        prev=fib(n-1,prevPrev);//递归计算前两项
        return prevPrev+prev;//其和即为正解
    }
}//用辅助变量记录前一项,返回数列的当前项,O(n);

实例 Fibonacci数列类,其中logN的复杂度可以使用矩阵快速幂来获得

class Fib{
    
    
    private:
    int f, g;//f=fib(k-1),g=fib(k)均为int型,很快就会数值溢出
    public: 
    Fib(int n){
    
    //初始化不小于n的最小Fibonacci项
        f=1;g=0;
        while(g<n) next();
    }
    int get(){
    
    return g;}
    int next(){
    
    g+=f;f=g-f;return g;}
    int prev(){
    
    f=g-f;g-=f;return g;}
};

矩阵快速幂求解Fibonacci数列

public int[][] matrixPower(int[][] m, int p){
    
    //矩阵快速幂
    int[][] res = new int[m.length][m[0].length]; 
    //先把res初始化为单位矩阵
    for (int i = 0; i < res.length; i++){
    
    
        res[i][i] = 1;
    }
    int[][] tmp = m;
    for (; p != 0; p >>= 1){
    
     //右移符号,相当于整除2
        if ((p&1) != 0){
    
    
            res = muliMatrix(res, tmp);
        }
        tmp = muliMatrix(tmp, tmp);
    }
    return res;
}

public int[][] muliMatrix(int[][] m1, int[][] m2){
    
    //矩阵乘法
    int[][] res = new int[m1.length][m2[0].length];
    for (int i = 0; i < m2[0].length; i++){
    
    
        for (int j = 0; j < m1.length; j++) {
    
    
            for (int k = 0; k < m2.length; k++){
    
    
                res[i][j] += m1[i][k] * m2[k][j];
            }
        }
    }
    return res;
}

public int calc(int n){
    
    //使用矩阵快速幂求解Fibonacci数列
    if (n < 1) {
    
    
        return 0;
    }
    if (n == 1 || n == 2){
    
    
        return 1;
    }
    int[][] base = {
    
    {
    
    1, 1} , {
    
    1, 0}};
    int[][] res = matrixPower(base, n - 2);
    return res[0][0] + res[1][0];
}

猜你喜欢

转载自blog.csdn.net/ZXG20000/article/details/112549937