递归——线性递归与二分递归

向右看齐 2022-05-21 04:41 426阅读 0赞

递归

#

线性递归

例子1:数组求和

  1. int sum( int A[], int n) { //数组求和算法:线性递归版
  2. if ( 1 > n ) //平凡情况,递归基
  3. return 0; //直接计算
  4. else //一般情况
  5. return sum(A, n-1) + A[n - 1]; //递归:前n-1项之和,再累计第n-1项
  6. }

每一递归实例对自身的调用至多一次。于是,每一层次上至多只有一个实例,且它们构成一个线性的次序关系。此类递归模式称作“线性递归”,这是递归最基本形式。

递归算法必须设置递归基,且确保总能执行到。针对每一类可能出现的平凡情况,都需设置对应的递归基。递归基可能不止一个。

例子2:数组倒置

数组倒置算法的统一入口:

  1. void reverse(int*, int, int); //重载的倒置算法原型
  2. void reverse(int* A, int n) //数组倒置
  3. { reverse(A, 0, n-1); } //由重载的入口启动递归或迭代算法
  4. void reverse(int* A, int lo, int hi) { //数组倒置(多递归基版)
  5. iflo < hi) {
  6. swap( A[li], A[hi] ); //交换
  7. reverse(A, lo+1, hi-1 ); //递归倒置A(lo,hi)
  8. } //else隐含了两种递归基
  9. }

尾递归及其消除

在线性递归算法中,若递归调用在递归实例中恰好以最后一步操作的形式出现,则称作尾递归。上述数组倒置算法就是典型的尾递归。

尾递归形式的算法,可以转换为等效的迭代版本:

  1. void reverse(int* A, int lo, int hi) { //数组倒置(迭代版)
  2. next: //算法起始位置添加跳转标志
  3. if (lo < hi) {
  4. swap( A[lo], A[hi] ); //交换
  5. lo ++; hi-- ; //收缩待倒置区间
  6. goto next; //跳转至起始位置
  7. } //else隐含了迭代的终止
  8. }

goto语句最好尽力回避,所以进一步调整,消除goto语句

  1. void reverse( int* A, int lo, int hi) {
  2. while(lo < hi)
  3. swap(A[lo++], A[hi--]);
  4. }

二分递归

每一递归实例可能做多次递归,故称作”多路递归“。通常都是将原问题一分为二,故称作”二分递归”。

例子1:数组求和(二分递归版)

  1. int sum(int A[], int lo, int hi) { //数组求和算法:二分递归版
  2. if (lo == hi)
  3. return A[lo]; //直接返回
  4. else {
  5. int mi = (lo + hi) >> 1; //以居中单元为界,将原区间一分为二
  6. return sum(A,lo,mi) + sum(A,mi+1,hi); //递归对各子数组求和,然后合计
  7. }
  8. }

例子2:Fibonacci数:二分递归

考查Fibonacci数列第n项fib(n)的计算问题,该数列定义如下:

fib(n) = n; n <= 1

fib(n) = fib(n-1) + fib(n-2); n>= 2

二分递归版:

  1. _int64 fib( int n ) { //计算Fibonacci数列的第n项
  2. return ( 2 > n ) ?
  3. (_int64) n //若到达递归基,直接取值
  4. : fib(n - 1) + fib(n - 2); //否则,递归计算前两项,其和即为正解

这种算法虽然简洁,但是效率及其低下,这是指数复杂度算法,时间复杂度为O(2^n)。

优化策略:

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

1.制表或记忆策略

线性递归版:

  1. _int64 fib(int n, _int64& prev) { //计算Fibonacci数列第n项:入口形式fib(n,prev)
  2. if ( n == 0) //若到达递归基
  3. { prev = 1; return 0; } //直接取值:fib(-1) = 1, fib(0) = 0
  4. else {
  5. _int64 prevPrev; prev = fib(n-1,prevPrev); //递归计算前两项
  6. return prevPrev + prev; //其和即为正解
  7. }
  8. }//用辅助变量记录前一项,返回数列的当前项,时间复杂度为O(n)

2.动态规划策略

在以上线性递归版fib()算法可见,其中所记录每一个子问题的解答,只会用到一次,在该算法抵达递归基之后的逐层返回过程中,每向上返回一层,以下各层的解答均不必继续保留。

迭代版:

  1. _int64 fibI( int n ) { //计算Fibonacci数列的第n项(迭代版):O(n)
  2. _int64 f = 1, g = 0; //初始化:fib(-1),fib(0)
  3. while(n-- > 0) { g += f; f = g - f; } //依据原始定义,通过n次加法和减法计算fib(n)
  4. return g;
  5. }

这里仅使用了两个中间变量f和g,记录当前的一对相邻的Fibonacci数。整个算法仅需线性步的迭代,时间复杂度为O(n)。

发表评论

表情:
评论列表 (有 0 条评论,426人围观)

还没有评论,来说两句吧...

相关阅读