我的Fibonacci序列作为递归函数是一个无限循环

时间:2014-08-21 16:36:05

标签: perl recursion fibonacci

以下函数无限地递归,我不明白为什么。它进入条件语句但似乎没有以return语句终止。

use strict;
use warnings;

print fibonacci(100);

sub fibonacci {

    my $number = shift;

    if ($number == 0) {
        print "return 0\n";
        return 0;
    }
    elsif ($number == 1) {
        print "return 1\n";
        return 1;
    }
    else {
        return fibonacci($number-1) + fibonacci($number-2);
    }
}

3 个答案:

答案 0 :(得分:5)

你的循环不会无限递归,输入为100只需要太长时间。尝试一个记忆版本:

{ my @fib;
  sub fib {
    my $n = shift;
    return $fib[$n] if defined $fib[$n];
    return $fib[$n] = $n if $n < 2;
    $fib[$n] = fib($n-1) + fib($n-2);
  }
}

答案 1 :(得分:3)

您的代码可以更加简洁,并通过记忆来大幅加速。

Memoize模块是迄今为止记忆(缓存子程序的结果)最简单,最清晰的方法。

我建议这个版本。它仍然警告深度递归,但它产生的值我认为是正确的。

use strict;
use warnings;

use Memoize;
memoize('fibonacci');

print fibonacci(100);

sub fibonacci {
    my ($n) = @_;
    $n < 2 ? $n : fibonacci($n-1) + fibonacci($n-2);
}

<强>输出

Deep recursion on anonymous subroutine at E:\Perl\source\fib.pl line 11.
Deep recursion on anonymous subroutine at E:\Perl\source\fib.pl line 11.
3.54224848179262e+020

答案 2 :(得分:2)

您的递归函数调用不是无限的。这需要很长时间。

一个人应该注意递归函数,因为如果它可以这样设计,通常可以更好地使用循环来编写代码。

在这种情况下,您对fibonacci的递归调用次数呈指数级增长。您可以通过简单地添加较低级别的呼叫

来自行测试
  • fibonacci(0) = 1致电
  • fibonacci(1) = 1致电
  • fibonacci(2) = 1次致电fibonacci(1) +致电fibonacci(0)→3
  • fibonacci(3) = 1次致电fibonacci(2) +致电fibonacci(1)→5
  • fibonacci(4) = 1次致电fibonacci(3) +致电fibonacci(2)→9
  • fibonacci(5) = 1次致电fibonacci(4) +致电fibonacci(3)→15
  • fibonacci(N) = 1次致电+致电fibonacci(N-1) +致电fibonacci(N-2)→?

正如你所看到的那样,子程序调用的数量会增加斐波纳契。

要确定fibonacci(100)的通话次数,我们可以简单地创建一个快速perl单行:

$ perl -e '
    @c = (1, 1); 
    $c[$_] = 1 + $c[$_-1] + $c[$_-2] for (2..100);
    print $c[100]
  ' 
1.14629568802763e+21

每秒拨打1万亿电话,仍需要超过31年才能完成。

使用Memoize修复

已经提出的一个修复是使用核心库Memoize

use Memoize;
memoize('fibonacci');

此模块将环绕您的子例程调用并缓存值的计算。因此,这将减少从1.14e21到101计算的函数调用次数。

但是,由于您的递归次数超过100次,因此每perldiag

会收到以下警告
  
      
  • 匿名子程序的深度递归
  •   
  • 子程序&#34;%s&#34;

    的深度递归      

    (W recursion)这个子程序自己(直接或间接)调用了100次以上的返回值。这可能表示无限递归,除非您正在编写奇怪的基准程序,在这种情况下它表示其他内容。

         

    通过重新编译perl二进制文件,将C预处理器宏PERL_SUB_DEPTH_WARN设置为所需的值,可以从100更改此阈值。

  •   

创建一个循环 - 避免递归

您可以非常轻松地从自上而下的计算方法到自下而上的方法重新设计算法。

这完全消除了递归的需要,并将代码减少到以下内容:

use strict;
use warnings;

print fibonacci(100), "\n";

sub fibonacci {
    my $number = shift;

    my @fib = ( 0, 1 );

    for ( $#fib + 1 .. $number ) {
        $fib[$_] = $fib[ $_ - 1 ] + $fib[ $_ - 2 ];
    }

    return $fib[$number];
}

输出:

3.54224848179262e+20

不会发出任何警告。