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递归分析
递归跟踪
- 算法的每一递归实例都表示为一个方框,其中注明了该实例调用的参数
- 若实例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.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)={n,n<=1fib(n−1)+fib(n−2),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];
}