为什么用 递归 计算“阶乘”和“斐波那契数列”是不合适的?

        我们看到的参考书中,当讲到递归时基本上都是使用“阶乘”和“斐波那契数列”来举例子,确实可以帮助我们了解递归,但却导致我们在平时编写类似程序时,总是使用递归来实现。那么在实际项目中,使用递归来实现到底是否合适?答案是否定 的。

        《C和指针》的作者Kenneth A. Reek说,他认为这是很不幸的:“计算阶乘时不能提供任何优越之处;在斐波那契数列中,使用递归效率非常非常低”。  尤其对于计算斐波那契数,使用递归和使用迭代的效率有可能相差几十万倍,下面有代码分析。

1. 技巧

    1.1 “尾部递归”可以使用“迭代”来替换。 学过数据结构之后,对这个应该不陌生。

    1.2   递归 的优点在于易于理解迭代 的优点在于效率高 。如何选择取决于你在“易于理解”和“效率”中如何作出权衡。

            递归 函数调用涉及一些运行时开销,包括参数必须压到堆栈中、为局部变量分配内存、寄存器的值必须保存等等,并且在每次调用返回时,上述保存操作必须还原,恢复成调用前的样子。迭代 的实现方式开销显然要小。所以在可理解性相差不大的情况下,为了保证效率应该优先选择迭代

2. 阶乘

         阶乘的一种定义方式为:

F(n) = 1        当 n<=0
F(n) = n * F(n-1)       当n>0

    2.1  递归实现

          实现代码如下

/* 利用 递归 来计算整数n的阶乘 */
long factorial_recursion( int n ){
if( n<=0 ){
return 1;
}else{
return n*factorial_recutsion( n-1 );
}
}

         很明显是“尾部递归”,所以我们可以很轻松的改写为“迭代”的形式。

    2.2   迭代实现

         迭代实现代码如下

/* 利用 迭代 来计算整数n的阶乘 */ 
long factorial_iteration( int n ){
int result = 1;
while( n>1 ){
result *=n;
n--;
}
return result;
}

    2.3   两种方式的比较

        很明显,两种实现方式都非常简单易懂,在实际项目中,为了效率,应该优先选择 迭代

3. 斐波那契数列

        斐波那契数列中的每个数,都是它前面两个数的和。其计算公式如下:

F(0) = 0
F(1) = 1
F(n)= F(n-1) + F(n-2)

    3.1  递归实现

          递归实现代码如下

/* 计算斐波那契数列的第num个数 */
int fibonacci_count( int num ){
if( num<=2 ){
return 1;
}else{
return fibonacci_count(num-1) + fibonacci_count(num-2);
}
}

    3.2  迭代实现

           迭代实现代码如下

/* 
利用 迭代 计算 斐波那契数
其形式为: 1, 1, 2, 3, 5, 8, 13, 21, 34 …
*/
long fibonacci(int n){
long result;
long previous;
long next;

result = previous = 1;

if(n<=2){
return 1;
}
int i =3; /* 用于计算已经计算到第几个 */
while( i<=n ){
next = result;
result = previous + next;
previous = next;
i++;
}
return result;
}

    3.3  两种方式比较

        下面一个程序反映了,利用递归方式计算斐波那契函数时,调用fibonacci函数的次数,以及计算fibonacci(3)的次数。

#include <stdio.h>
#include <stdlib.h>

int fibonacci_count( int num );

long count; /* 用来记录斐波那契数列中计算调用 fibonacci_rec_count函数 的次数 */
int num_three; /* 计算 fibonacci_rec_count(3)的次数 */
int main(){
printf("Fibonacci(num)/t/tNumber of Calls/t/tnumbers of Call fibonacci_rec_count(3) /n");
int i;
for( i=1; i<=30; i++ ){
int result = fibonacci_rec_count(i);
printf("num: %d/t/t/tcount: %ld /t/t%d/n", i, count, num_three);
count = 0;
num_three = 0;
}

getchar();
return EXIT_SUCCESS;
}
/* 计算斐波那契数列的第num个数 */
int fibonacci_rec_count( int num ){
count++; /* 每调用一次fibonacci_rec_count函数,count的值便加1 */
if( num==3 ){
num_three++;
}
if( num<=2 ){
return 1;
}else{
return fibonacci_rec_count(num-1) + fibonacci_rec_count(num-2);
}
}

运行结果为:

Fibonacci(num)          Number of Calls         numbers of fibonacci(3)
num: 1 count: 1 0
num: 2 count: 1 0
num: 3 count: 3 1
num: 4 count: 5 1
num: 5 count: 9 2
num: 6 count: 15 3
num: 7 count: 25 5
num: 8 count: 41 8
num: 9 count: 67 13
num: 10 count: 109 21
num: 11 count: 177 34
num: 12 count: 287 55
num: 13 count: 465 89
num: 14 count: 753 144
num: 15 count: 1219 233
num: 16 count: 1973 377
num: 17 count: 3193 610
num: 18 count: 5167 987
num: 19 count: 8361 1597
num: 20 count: 13529 2584
num: 21 count: 21891 4181
num: 22 count: 35421 6765
num: 23 count: 57313 10946
num: 24 count: 92735 17711
num: 25 count: 150049 28657
num: 26 count: 242785 46368
num: 27 count: 392835 75025
num: 28 count: 635621 121393
num: 29 count: 1028457 196418
num: 30 count: 1664079 317811

        在递归实现方式中,每次递归调用都会触发另外两个递归调用,而且这两个调用的任何一个又会触发两个递归调用,依次越来越多。冗余的计算增长迅速。例如:在 计算fibonacci(10) 时,fibonacci(3)被计算了21次;但在计算fibonacci(30) 时,fibonacci(3)被计算了 317811 次;这 317811 次计算结果完全一样,除了一次有用外,其余都是浪费。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值