珀尔 |函数递归中的尾调用
Perl 中的递归是任何不使用for
循环或while
循环的函数,而是在程序执行期间调用自身,相应的函数称为递归函数。
尾递归函数是递归的一种特殊情况,其中函数调用语句作为过程的最终动作执行。所以return loop(..);
会工作,但return loop() + operation;
不会。尾递归(或尾端递归)特别有用,并且在实现中通常很容易处理。尾递归的实现主要是为了避免堆栈数据结构占用空间,堆栈数据结构跟踪之前递归调用语句返回的数据。
以下是一些示例,可以更好地理解该概念:示例 1:
#!/usr/bin/perl
# Perl Program to calculate Factorial
# Recursion without tail call
sub recurse_fact
{
my $x = $_[0];
# checking if that value is 0 or 1
if ($x == 0 || $x == 1)
{
return 1;
}
# Recursively calling function with
# the next value which is one less
# than current one
else
{
return $x * recurse_fact($x - 1);
}
}
# Recursion using Tail Call
sub tail_recurse_fact
{
my ($ans, $x) = @_;
# checking if that value is 0 or 1
if ($x == 0 || $x == 1)
{
return $ans;
}
# Recursively calling function with
# the next value which is one less
# than current one
else
{
return tail_recurse_fact($ans * $x, $x - 1);
}
}
# Driver Code
$a = 5;
# Function call and printing result after return
print "Factorial of a number $a ",
"through recursion is ", recurse_fact($a);
print "\nFactorial of a number $a ",
"through tail-recursion is ",
tail_recurse_fact(1, $a);
Factorial of a number 5 through recursion is 120
Factorial of a number 5 through tail-recursion is 120
在非尾版本中,编译器需要跟踪你要与之相乘的数字,而在尾调用版本中,编译器可以意识到唯一剩下要做的就是另一个函数调用,它可以忘记当前函数中使用的所有变量和状态。
示例 2:
#!/usr/bin/perl
# Perl program to demonstrate the
# use of tail-recursion
use strict;
use warnings;
sub recurse_fib
{
my $n = shift;
if ($n == 1 or $n == 2)
{
return 1
}
# recursive calling
return (recurse_fib($n - 1) +
recurse_fib($n - 2));
}
sub tail_recurse_fib
{
my ($n, $a, $b) = @_;
if ($n == 1)
{
return $a
}
if ($n == 2)
{
return $b
}
else
{
# tail recursive calling
return tail_recurse_fib($n - 1, $b, $a + $b)
}
}
# Driver code
$a = 10;
print "Fibonacci upto $a through recursion is ",
recurse_fib($a);
print "\nFibonacci upto $a through tail-recursion is ",
tail_recurse_fib($a, 1, 1);
Fibonacci upto 10 through recursion is 55
Fibonacci upto 10 through tail-recursion is 55
使用goto
语句演示尾递归: goto
会将编译器从当前运行的子程序转移到给定名称的子程序。这将替换函数调用并以相同的方式创建递归过程。
# Perl program to demonstrate the
# use of tail-recursion
use warnings;
sub recurse
{
my $i = shift;
return if $i == 0;
recurse($i - 1);
}
sub tail_recurse
{
my $i = shift;
return if $i == 0;
@_ = ($i - 1);
goto &tail_recurse;
}
# Driver Code
print "recursing\n";
recurse(200);
print "tail_recursing\n";
tail_recurse(200);
输出:
recursing
tail_recursing
在上面的示例中, recurse
函数将产生“深度递归”的致命错误,而tail_recurse
将正常工作。
消除尾声
尾递归优于非尾递归,因为尾递归可以被现代编译器优化。现代编译器为优化尾递归代码所做的工作称为尾调用消除。尾调用消除节省了堆栈空间。它用goto语句替换函数调用。这真的不是消除——它是一种优化。
有时深度递归方法是解决问题的最简单方法,但如果递归超过几百个调用,就会遇到 Perl 的“深度递归”错误。这就是为什么在 Perl 中使用尾递归而不是非尾递归代码的原因。
如果我们仔细看看上面讨论的函数,我们可以用 goto 删除最后一个调用。以下是尾调用消除的示例。
示例 1:数字的阶乘
#!/usr/bin/perl
# Perl Program to calculate Factorial
sub tail_recurse_fact
{
my $ans = shift;
my $x = shift;
# checking if that value is 0 or 1
if ($x == 0 || $x == 1)
{
return $ans;
}
# Recursively calling function with
# the next value which is one less
# than current one
else
{
@_ = ($x * $ans, $x - 1);
goto &tail_recurse_fact;
}
}
# Driver Code
$a = 5;
# Function call and printing result after return
print "Factorial of a number $a ",
"through tail-recursion is ",
tail_recurse_fact(1, $a);
Factorial of a number 5 through tail-recursion is 120
示例 2:斐波那契最高 n
#!/usr/bin/perl
# Perl program to demonstrate the
# use of tail-recursion-elimination
use strict;
use warnings;
sub tail_recurse_fib
{
my $n = shift;
my $a = shift;
my $b = shift;
if ($n == 1)
{
return $a
}
if ($n == 2)
{
return $b
}
else
{
@_ = ($n - 1, $b, $a + $b);
# tail recursive calling
goto &tail_recurse_fib;
}
}
# Driver code
$a = 10;
print "Fibonacci upto $a through tail-recursion is ",
tail_recurse_fib($a, 1, 1);
Fibonacci upto 10 through recursion is 55
Fibonacci upto 10 through tail-recursion is 55