Perl 我的斐波那契序列作为递归函数是一个无限循环

Perl 我的斐波那契序列作为递归函数是一个无限循环,perl,recursion,fibonacci,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) { prin

下面的函数无限递归,我不明白为什么。它输入条件语句,但似乎不会以
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);
    }
}

您的循环不是无限递归的,它只是在输入为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);
  }
}
{my@fib;
次级纤维{
我的$n=班次;
如果定义了$fib[$n],则返回$fib[$n];
如果$n<2,则返回$fib[$n]=$n;
$fib[$n]=fib($n-1)+fib($n-2);
}
}

通过记忆代码,可以使代码更加简洁,并大大加快速度

该模块是迄今为止记忆(缓存)子例程结果的最简单、最清晰的方法

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

use strict;
use warnings;

use Memoize;
memoize('fibonacci');

print fibonacci(100);

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

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

应该注意递归函数,因为如果可以这样设计的话,通常最好使用循环来编写代码

在这种情况下,对
fibonacci
的递归调用数呈指数增长。通过简单地将较低级别的调用相加,您可以很容易地测试这一点

  • fibonacci(0)
    =1次调用
  • fibonacci(1)
    =1次呼叫
  • fibonacci(2)
    =1次调用+调用
    fibonacci(1)
    +调用
    fibonacci(0)
    → 三,
  • fibonacci(3)
    =1次呼叫+呼叫
    fibonacci(2)
    +呼叫
    fibonacci(1)
    → 五,
  • fibonacci(4)
    =1次呼叫+呼叫
    fibonacci(3)
    +呼叫
    fibonacci(2)
    → 九,
  • fibonacci(5)
    =1次呼叫+呼叫
    fibonacci(4)
    +呼叫
    fibonacci(3)
    → 十五
  • fibonacci(N)
    =1次调用+调用
    fibonacci(N-1)
    +调用
    fibonacci(N-2)
    → ?
正如您所看到的,子程序调用的数量呈斐波那契式增长

要确定对
fibonacci(100)
的调用次数,我们只需创建一个快速的perl one liner:

$ perl -e '
    @c = (1, 1); 
    $c[$_] = 1 + $c[$_-1] + $c[$_-2] for (2..100);
    print $c[100]
  ' 
1.14629568802763e+21
以每秒1万亿次的速度,这仍然需要31年多的时间才能完成

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

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

但是,由于递归次数超过100次,因此每次都会收到以下警告

  • 匿名子例程的深度递归
  • 子例程“%s”上的深度递归

    (W递归)此子例程(直接或间接)调用自身的次数是其返回次数的100倍。这可能表示无限递归,除非您正在编写奇怪的基准测试程序,在这种情况下,它表示其他内容

    通过重新编译perl二进制文件,将C预处理器宏perl_SUB_DEPTH_WARN设置为所需值,可以将该阈值从100更改为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

不会给出任何警告。

尝试一个小得多的数字作为输入。它不是无限循环;这需要很长的时间(并且可能会触发检测无限循环的检查,因为它运行堆栈的深度)。如果您正在玩代码高尔夫,重写函数体是可以的,但在其他方面是不必要的。只需在OP的定义上使用
memoize
,它就可以加速到在我的机器上不到15毫秒的时间内返回第100个斐波那契数。@MarkReed:这远远不是代码高尔夫。所有必要的空白都已准备就绪,以明确其含义。如果您对条件运算符感到不舒服,那么我表示同情,但是习惯于阅读Perl代码的人会发现它比14行原始代码更容易阅读;我只是说,性能提升不需要改变。:)@马克·里德:不,马克,你说这个变化“如果你在打代码高尔夫的话没问题,但在其他方面是不必要的”。这与“性能提升不需要”不同。在我的机器上,这种手动记忆的性能比
Memoize
的性能高出2倍,尽管对于使用模块的自动化和一致性有一些说法。@MarkReed:我怀疑你是否会发现很多人关心他们是在15毫秒还是7毫秒内得到结果。@MarkReed Memoize使用哈希实现缓存,因为它涵盖了一般情况。在这个特定示例中使用数组可能是更高性能的来源。
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