📜  珀尔 |函数递归中的尾调用

📅  最后修改于: 2022-05-13 01:55:23.376000             🧑  作者: Mango

珀尔 |函数递归中的尾调用

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