函数-2:点此转入
6.递归与非递归
6.1 练习3
求n的阶乘。(不考虑溢出)
参考代码:
int fact(int n)
{
if (n > 1)
return n * fact(n - 1);
else
return 1;
}
6.2 练习4
求第n个斐波那契数
求第n个斐波那契数首先我们要了解什么斐波那契数列:
1 1 2 3 5 8 13 21 34 55
我们发现:从3开始,第n个斐波那契数等于前两个斐波那契数之和
即Fib(n) = F(n-1) + F(n-2) (n>2)。
所以我们就可以写出代码:
int Fib(int n)
{
if (n > 2)
return Fib(n - 1) + Fib(n - 2);
else
return 1;
}
6.3 问题
如上两个函数我们发现有问题:
- 在使用Fib这个函数的时候如果我们要计算第50个斐波那契数字的时候特别浪费时间。
- 在使用fact函数求10000的阶乘(不考虑结果的正确性)。
为什么呢?
- 我们发现Fib函数在调用的过程中很多计算其实一直在重复。如果我们把代码修改一下:
long long int count = 0;
int Fib(int n)
{
//计算Fib(3)运行了多少次
if (n == 3)
count++;
if (n > 2)
return Fib(n - 1) + Fib(n - 2);
else
return 1;
}
然后我们打印count的值
我们可以看到仅仅是计算第40个斐波那契数,Fib(3)被计算的次数就高达3900多万次,
所以利用递归来求第n个斐波那契数运算量是特别大的,效率也是比较低的。
那我们该如何改进呢?
- 在调试fact函数的时候,如果你的参数比较大,那就会报错:stack overflow(栈溢出)这样的信息。
- 系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。
那如何解决上述的问题:
- 将递归改写成非递归。
- 使用static对象替代nonstatic局部对象。在递归函数设计中可以使用static对象替代nonstatic局部对象(即栈对象)这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
现在我们试试用非递归的方式来改写代码:
//求n的阶乘
int fact(int n)
{
int i = 0;
int ret = 1;
for (i = 1; i <= n; i++)
{
ret *= i;
}
return ret;
}
//求第n个斐波那契数
int Fib(int n)
{
int a = 1, b = 1, c = 0;
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return b;
}
总结:
- 许多问题是以递归的形式进行解释的,这只是因为他比非递归的方式更为清晰。
- 但是这些问题的非递归实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
- 当一个问题相当复杂,难以用非递归实现时,此时递归的简洁性便可以补偿它所带来的运行时开销。