【问题标题】:How to recognize what is, and what is not tail recursion?如何识别什么是尾递归,什么不是尾递归?
【发布时间】:2011-04-10 08:45:10
【问题描述】:

有时它很简单(如果 self 调用是最后一个语句,它就是尾递归),但仍有一些情况让我感到困惑。一位教授告诉我“如果在自调用之后没有执行指令,那就是尾递归”。这些例子怎么样(忽略它们没有多大意义的事实):

a) 这个应该是尾递归的,看看self-call是怎么做的最后一个语句,之后就没有什么可以执行了。

function foo(n)
{
    if(n == 0)
        return 0;
    else
        return foo(n-2);
}

b) 但是这个呢?应该是尾调用,因为如果条件为真,除了它什么都不会被执行,但不是最后一条语句?

function foo(n)
{
    if(n != 0)
        return foo(n-2);
    else
        return 0;
}

c) 这个怎么样?在这两种情况下,self 调用都是最后执行的:

function foo(n)
{
    if(n == 0)
        return 0;
    else    
    {
        if(n > 100)
            return foo(n - 2);
        else
            return foo(n - 1);
    }
}

【问题讨论】:

    标签: algorithm recursion tail-recursion tail-call


    【解决方案1】:

    从如何实际实现尾调用优化的角度考虑这一点可能会帮助您。当然,这不是定义的一部分,但它确实激发了定义。

    通常在调用函数时,调用代码会将以后需要的任何寄存器值存储在堆栈中。它还将存储一个返回地址,指示调用后的下一条指令。它将做任何需要做的事情来确保为被调用者正确设置堆栈指针。然后它会跳转到目标地址[*](在这种情况下,同样的函数)。返回时,它知道返回值在调用约定指定的位置(寄存器或堆栈槽)。

    对于尾调用,调用者不会这样做。它忽略任何寄存器值,因为它知道以后不需要它们。它设置堆栈指针,以便被调用者将使用与调用者相同的堆栈,并且它不会将自己设置为返回地址,它只是跳转到目标地址。因此,被调用者将覆盖相同的堆栈区域,它会将其返回值放在调用者放置其返回值的相同位置,并且当它返回时,它不会返回给它的调用者,而是会返回到它的调用者的来电者。

    因此,非正式地,当尾调用优化可能发生并且尾调用的目标是函数本身时,函数是尾递归的。效果或多或少与函数包含循环相同,并且尾调用不是调用自身,而是跳转到循环的开头。这意味着调用之后必须不需要任何变量(实际上没有“工作要做”,这在像 C++ 这样的语言中意味着什么都不会被破坏),并且尾调用的返回值必须由调用者返回。

    这都是为了简单/琐碎的尾递归。有一些转换可以用来制作一些尚未尾递归的东西,例如引入额外的参数,这些参数存储“最底层”递归级别使用的一些信息,以完成本来可以完成的工作出路”。比如:

    int triangle(int n) {
        if (n == 0) return 0;
        return n + triangle(n-1);
    }
    

    可以由程序员或由足够聪明的编译器自动实现尾递归,如下所示:

    int triangle(int n, int accumulator = 0) {
        if (n == 0) return accumulator;
        return triangle(n-1, accumulator + n);
    }
    

    因此,谈论足够智能的语言/编译器的人可能会将前一个函数描述为“尾递归”。为这种变体用法做好准备。

    [*] 存储返回地址、移动堆栈指针和跳转,可能会或可能不会被架构包含在单个操作码中,但即使不是这样,通常也会发生这种情况。

    【讨论】:

      【解决方案2】:

      是的;我认为您的教授的意思是,在任何路径中,如果最终指令是递归的,那么它就是尾递归。

      因此,所有三个示例都是尾递归的。

      【讨论】:

        【解决方案3】:

        所有三个示例都是尾递归的。一般来说,它是尾递归,如果函数的结果(“return”关键字后面的表达式)是对函数本身的单独调用。 表达式的最外层不得涉及其他运算符。如果对自身的调用只是表达式的一部分,那么机器必须执行调用,然后必须返回到对所述表达式的评估中,也就是说,它不是在函数执行的尾部,而是在一种表达。但是,这不适用于递归调用可能采用的任何参数:那里允许任何参数,包括对其自身的递归调用(例如“return foo(foo(0));”)。当然,跳转调用的优化只能用于外部调用。

        【讨论】:

          【解决方案4】:

          你所有的函数都是尾递归的。

          自调用后没有指令

          意思是:自调用后,你从函数中返回,即不再需要执行代码,并不是说函数中没有更多的代码行了。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-11-17
            • 1970-01-01
            • 2013-09-14
            • 1970-01-01
            • 2011-01-25
            • 1970-01-01
            相关资源
            最近更新 更多