C++抽象编程——递归简介(4)——斐波那契函数的分析与扩展

由上一篇的文章分析,我们可以对这个数列进行编码,输出前20位元素,代码如下:

#include <iostream>
#include <iomanip>
using namespace std;
const int MIN_INDEX = 10;
const int MAX_INDEX = 20;
/*函数原型声明*/
int fib(int n);
int main (){
	cout << "这个程序列出了10到20的斐波那契数列;" << endl;
	for(int i = MIN_INDEX; i <= MAX_INDEX; i++){   /*锁定范围*/ 
	if(i < 10) cout << " ";
	cout << "fib(" << i << ")";
	cout << "=" << setw(4) << fib(i) << endl;  /*格式化输出*/ 
	}
	return 0;
}
/*函数定义
 *此时fib(1)=1;
 *fib(2)=2;
 *fib(n)=fib(n-1)+fib(n-2)
 */ 
int fib(int n){
	if(n==0 || n==1) return n;    /*simple case*/ 
	else {
		return fib(n-1) + fib(n-2);    /*recursive decomposition*/
	} 
}

程序执行情况如下(在devcpp下):


Gaining confidence in the recursive implementation

现在,你已经有了 fib 函数了,并且它也能正确的运行了,但你是否还是不能说服自己它是怎么运行的?你当然也可以通过追踪它的逻辑来找寻。举个例子,当你执行fib(5)的时候,由于他不是simple case,所以不执行if 语句,进而执行else语句中的语句

return fib(n - 1) + fib(n - 2);

这句话等价于:

return fib(4) + fib(3);

此时程序计算fib(4)的值并加上 fib(3)的值,那么fib(4)跟fib(3)的值是多少呢?利用同样的步骤往下计算,一直到他计算到 simple case 时候,就返回了一个确切的值,这个时候我们就可以计算出fib(5)的确切值了;当然,我们也可以利用leap of faith 来假设整个递归程序的值都是正确的,那么 fib(4)=3;fib(3)=2;所以fib(5)=3+2=5;所以,我们强调,递归中,你们不需要理解太多的计算细节,最好把它留给计算机吧(You don’t need to see all the details, which are best left to the computer)

Efficiency of the recursive implementation

如果你深入的去计算执行fib(5)的操作,你就会发现这个做法是很不高效的。原因就是这个递归的分解部分存在很多的递归调用,并且在调用之后还需要计算返回的值,计算fib(5)的值,那么你还要计算fib(4)和 fib(3)的值,要计算他们的值,你还必须执行到 simple case,使用循环反而不用考虑这些因素的影响。具体的可以通过下图来说明一番:


类似于树状。

Recursion is not to blame

虽然上面的代码并不是高效的算法,但是确实很经典的递归例子。通过其他的策略,我们是可以写出一个高效的fib函数的。但是我们学习递归的主要目的,就是找出更高效的解决办法去解决更一般的问题。比如,我们所看到的斐波那契数列,并不是符合递推关系的唯一的一串数组,往下看

递推关系  :       


传统的斐波那契数列为

(这个时候,是从0,1开始的)

但是我们可以让 t0= 3,t1=7,就可以得到一个满足条件的新的数列,为


同样的,让t0=-1  t1=2,我们可以得到下面的数组:


这几个数组,唯一的区别就是他们的首元素的取值不同而已。像这种,满足这种模式的数组我们称为加法数组(As a general class, the sequences that follow this
pattern are called additive sequences
.)

有了加法数组的概念,我们就可以将问题转化成,在n多个加法数组中寻找我们的斐波那契数列,或者更,或者更通俗的讲,就是在加法数组中找到第n个数的值。因此我们可以考虑这个函数的原型,首先,我们第一个变量要用来接收我们要找的那个数是第几个,然后我们必须有两个值来初始化这个数组即我们必须要知道开头的两个首元素,所以我们可以很轻松的写出这个函数原型:

int additiveSequence(int n, int t0, int t1);

如果你有这个函数 ,那你要做的就只需要输入数列的首两个元素就可以了,在斐波那契数列中可以这样写:

int fib(int n) {
return additiveSequence(n, 0, 1);
}

这个函数体里,只有很简单的一行代码,而且仅仅是调用函数而已,顺着一些其他的内容。这种只是简单的返回其他函数的值,执行其他函数的代码的函数,我们称包装函数(Functions of this sort, which simply return the result ofanother function, often after transforming the arguments in some way, arecalled wrapper functions.),这些函数提供了解决更为一般的问题的函数,在递归中是非常常见的。

   到了这里,我们剩下的任务就是编写 additiveSequence函数了。如果你认真思考这个更加一般的情况,你就会发现加法数组具有他们自己的递归特征,比如他们的simple case就是 t0 跟 t1.,而你要求某一个数的,那么你就必须知道前者的值。但是如果你被进一步问到在数组去找一个更进一步的值呢?例如,在初始值为3和7的加法数组中找到t6的值。我们可以通过上面的图找到 t6=71. 而这个有趣的问题就是,你怎么利用递归的方法去找到并返回这个值呢?

   这一步的关键就是我们要能观察出,在加法数组中,第n项(term)就是第n-1项的简化版,此时进一步开始第一个步骤。举个例子,上面的t6,就是简化后的表中的t5,简化表如下:


你也可以把它看做就是把原来的表格往前移动了,把原来t1的值赋给了此时的t0,把原来t2的值赋给了t1。此时表格从 7和 10 开始。所以我们可以这样来编写additiveSequence函数

int additiveSequence(int n, int t0, int t1) {
if (n == 0) return t0;
if (n == 1) return t1;
return additiveSequence(n - 1, t1, t0 + t1);
}

如果我们看懂或者我们还是有点小迷茫,我们可以用 leap of faith 来看看,比如fib(5),执行的过程如下:

fib(5)
= additiveSequence(5, 0, 1)
= additiveSequence(4, 1, 1)
= additiveSequence(3, 1, 2)
= additiveSequence(2, 2, 3)
= additiveSequence(1, 3, 5)
= 5

这样子的话,效率比上面的递归和一般的循环都高上不少。

下一篇我们将讲两个经典的递归使用和查找,并且分析一波递归的思想。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值