Algorithm 什么是尾部递归?

Algorithm 什么是尾部递归?,algorithm,language-agnostic,functional-programming,recursion,tail-recursion,Algorithm,Language Agnostic,Functional Programming,Recursion,Tail Recursion,在开始学习lisp时,我遇到了术语“尾部递归”。这到底是什么意思?我不是Lisp程序员,但我认为这会有所帮助 基本上,这是一种编程风格,递归调用是您最后要做的事情。使用常规递归,每次递归调用都会将另一个条目推送到调用堆栈上。当递归完成时,应用程序必须将每个条目都弹出并返回 使用尾部递归,根据语言的不同,编译器可能能够将堆栈向下折叠为一个条目,因此可以节省堆栈空间……大型递归查询实际上可能导致堆栈溢出 基本上,尾部递归可以优化为迭代。尾部递归是指递归算法中最后一条逻辑指令中的最后一条递归调用 通常

在开始学习lisp时,我遇到了术语“尾部递归”。这到底是什么意思?

我不是Lisp程序员,但我认为这会有所帮助


基本上,这是一种编程风格,递归调用是您最后要做的事情。

使用常规递归,每次递归调用都会将另一个条目推送到调用堆栈上。当递归完成时,应用程序必须将每个条目都弹出并返回

使用尾部递归,根据语言的不同,编译器可能能够将堆栈向下折叠为一个条目,因此可以节省堆栈空间……大型递归查询实际上可能导致堆栈溢出


基本上,尾部递归可以优化为迭代。

尾部递归是指递归算法中最后一条逻辑指令中的最后一条递归调用

通常在递归中,有一个基本情况,即停止递归调用并开始弹出调用堆栈。使用一个经典示例,虽然比Lisp更像C-ish,但阶乘函数演示了尾部递归。递归调用在检查基本情况条件后发生

factorial(x,fac=1){
如果(x==1)
返回fac;
其他的
收益阶乘(x-1,x*fac);
}

对阶乘的初始调用是
factorial(n)
,其中
fac=1
(默认值),n是要计算阶乘的数字。

传统递归中,典型的模型是首先执行递归调用,然后获取递归调用的返回值并计算结果。通过这种方式,只有在每次递归调用返回后,才能得到计算结果

在尾部递归中,首先执行计算,然后执行递归调用,将当前步骤的结果传递到下一个递归步骤。这导致最后一条语句的形式为
(return(递归函数参数))
基本上,任何给定递归步骤的返回值都与下一个递归调用的返回值相同


这样做的结果是,一旦准备好执行下一个递归步骤,就不再需要当前堆栈帧。这允许进行一些优化。事实上,使用适当编写的编译器,您永远不会有带有尾部递归调用的堆栈溢出窃笑器。只需在下一个递归步骤中重用当前堆栈帧。我很确定Lisp能做到这一点。

下面是一个例子,而不是用文字来解释它。这是阶乘函数的方案版本:

(定义(阶乘x)
(如果(=x0)1
(*x(阶乘(-x1(())))
下面是一个尾部递归的阶乘版本:

(定义阶乘)
letrec((事实)(λx累计)
(如果(=x 0)累计
(事实(-x1)(*累计x(()())))
(λ(x)
(事实x1)

在第一个版本中,您会注意到对事实的递归调用被馈送到乘法表达式中,因此在进行递归调用时,状态必须保存在堆栈上。在尾部递归版本中,没有其他S表达式等待递归调用的值,并且由于没有进一步的工作要做,因此状态不必保存在堆栈上。通常,Scheme尾部递归函数使用常量堆栈空间。

行话文件对尾部递归的定义有如下描述:

尾部递归/n/


如果您还没有厌倦它,请参阅尾部递归

这本选自Lua编程的书展示了(在Lua中,但也应该适用于Lisp)以及为什么它更好

tail调用[tail递归]是一种goto调用 作为一个电话。尾部调用发生在 函数调用另一个函数作为其最后一个函数 行动,所以它没有别的事可做。 例如,在下面的代码中, 对
g
的调用是尾部调用:

函数f(x)
返回g(x)
结束
f
调用
g
之后,它就没有别的了 去做。在这种情况下,程序 不需要返回调用 调用函数时的函数 末端。所以在尾声之后,, 该程序不需要保留任何 有关调用函数的信息 在堆栈中

因为正确的尾部调用不使用 堆栈空间,对 一个 程序可以使你的生活更美好。例如,我们可以 使用任意函数调用以下函数 作为参数的数字;永远不会 溢出堆栈:

函数foo(n) 如果n>0,则返回foo(n-1)end 结束 。。。正如我前面所说,尾部调用是 有点走投无路。因此,这是一个非常有用的方法 正确的尾部调用在计算机中的应用 Lua用于编程状态机。 这样的应用程序可以代表每个应用程序 用函数表示状态;改变状态 是去(或打电话)一个特定的 功能。作为一个例子,让我们 考虑一个简单的迷宫游戏。迷宫 有多个房间,每个房间最多 四扇门:北、南、东、北 西部。在每个步骤中,用户输入一个 运动方向。如果有门 在这个方向上,用户转到 相应的房间;否则 程序打印一条警告。目标是 从最初的房间到最后的房间 房间

这个游戏是典型的状态机, 其中当前房间为状态。 我们可以用一个来实现这样的迷宫 每个房间的功能。我们用尾巴 从一个房间移动到另一个房间的呼叫 另一个有四个房间的小迷宫 可能是这样的:

def recursiveFunction(some_params):
    # some code here
    return recursiveFunction(some_args)
    # no code after the return statement
功能室1()
本地移动=io.read()
如果move==“south”,则返回room3()
其他移动==“东”,然后返回房间2()
else打印(“无效移动”)
返回房间1()--呆在同一个房间
结束
结束
def tailrecsum(x, running_total=0):
  if x == 0:
    return running_total
  else:
    return tailrecsum(x - 1, running_total + x)
function N(x, p) {
   return x == 1 ? p : N(x - 1, p * x);
}
function N(x) {
   return x == 1 ? 1 : x * N(x - 1);
}
function F(x) {
  if (x == 1) return 0;
  if (x == 2) return 1;
  return F(x - 1) + F(x - 2);
}
  factorial(n):

    if n==0 then 1

    else n*factorial(n-1)
       factorial(4)
       /        \
      4      factorial(3)
     /             \
    3          factorial(2)
   /                  \
  2                factorial(1)
 /                       \
1                       factorial(0)
                            \
                             1    
factAux(m,n):
if n==0  then m;
else     factAux(m*n,n-1);

factTail(n):
   return factAux(1,n);
factTail(4)
   |
factAux(1,4)
   |
factAux(4,3)
   |
factAux(12,2)
   |
factAux(24,1)
   |
factAux(24,0)
   |
  24
public static int fact(int n){
  if(n <=1)
     return 1;
  else 
     return n * fact(n-1);
}
if(n <=1)
     return 1;
else 
     return n * fact(n-1);
public static int fact(4){
  if(4 <=1)
     return 1;
  else 
     return 4 * fact(4-1);
}
public static int fact(3){
  if(3 <=1)
     return 1;
  else 
     return 3 * fact(3-1);
}
public static int fact(2){
  if(2 <=1)
     return 1;
  else 
     return 2 * fact(2-1);
}
public static int fact(1){
  if(1 <=1)
     return 1;
  else 
     return 1 * fact(1-1);
}
public static int fact(x, running_total=1) {
    if (x==1) {
        return running_total;
    } else {
        return fact(x-1, running_total*x);
    }
}

public static int fact(4, running_total=1) {
    if (x==1) {
        return running_total;
    } else {
        return fact(4-1, running_total*4);
    }
}
public static int fact(3, running_total=4) {
    if (x==1) {
        return running_total;
    } else {
        return fact(3-1, 4*3);
    }
}
public static int fact(2, running_total=12) {
    if (x==1) {
        return running_total;
    } else {
        return fact(2-1, 12*2);
    }
}
public static int fact(1, running_total=24) {
    if (x==1) {
        return running_total;
    } else {
        return fact(1-1, 24*1);
    }
}
def recursiveFunction(some_params):
    # some code here
    return recursiveFunction(some_args)
    # no code after the return statement
def factorial(number):
    if number == 1:
        # BASE CASE
        return 1
    else:
        # RECURSIVE CASE
        # Note that `number *` happens *after* the recursive call.
        # This means that this is *not* tail call recursion.
        return number * factorial(number - 1)
def factorial(number, accumulator=1):
    if number == 0:
        # BASE CASE
        return accumulator
    else:
        # RECURSIVE CASE
        # There's no code after the recursive call.
        # This is tail call recursion:
        return factorial(number - 1, number * accumulator)
print(factorial(5))
    void print(int n) 
{ 
if (n < 0)  return; 
cout << " " << n; 
print(n-1);} 



// The last executed statement is recursive call 
g(a, b, n) = a * b^n
g(a, b, n) | n is zero = a
           | n is odd  = g(a*b, b,   n-1)
           | otherwise = g(a,   b*b, n/2)
g'(a, b, n) = a * f(b, n)
f(b, n) | n is zero = 1
        | n is odd  = f(b, n-1) * b
        | otherwise = f(b, n/2) ^ 2