我正在为Scala的考试做复习,并试图找出我错过的测验问题。我将尾递归理解为“最后一次调用本身”,但是我对其中一些代码片段之间的差异感到困惑。 为什么将此视为尾递归,
def f(x: Int): Int = {
if (x % 2 == 0) {1}
else {f(x + 1)}
但这不是吗?
def f(x: Int): Int = {
if (x % 2 == 0) {1}
else {1 + f(x + 1)}
在函数中加1到底是什么限制了它不能进行尾递归?很抱歉,如果这是一个愚蠢的问题,我看不到这个数字有何影响,并希望巩固我的理解。完全能够识别尾递归的任何其他指标也很棒!
答案 0 :(得分:6)
您的理解不太正确。尾递归并不意味着“最后一次呼叫本身”,而是“最后一次呼叫本身”。也就是说,尾递归调用必须是该函数执行的最后一个动作。它必须是函数在该代码路径上执行的操作列表的“尾部”。 (当然,必须有一个不包含递归调用的代码路径。)
考虑表达式中的值如何求值,而不是它们在代码中出现的顺序,也很重要。这个表情
1 + f(x + 1)
按以下顺序执行:
tmp1 = x + 1
tmp2 = f(tmp1)
res = 1 + tmp2
或者,
add(1, f(add(x, 1))
这样写,您可以看到对f
的调用之后是另一个动作,即最后的+
/ add
。由于递归调用不是最后的动作,所以它不是尾递归。
答案 1 :(得分:5)
在第二个片段中,最后一次通话不是“自身”,而是+
。
您必须先从f(x+1)
获取结果,然后然后将其加1,然后再返回。因此,当前帧必须保留在堆栈中以进行f(x+1)
调用。
相反,在第一个代码段中,f(x+1)
被调用后没有任何事情要做,因为其结果成为返回值。因此,编译器可以在进行调用之前从堆栈 中删除当前帧,以便将f(x+1)
调用的结果直接返回给f(x)
的调用者。>
答案 2 :(得分:2)
通常可以用函数调用符号将所有内容明确记下来:
def f(x: Int): Int =
if (==(%(x, 2), 0) 1
else f(+(x, 1))
def f(x: Int): Int =
if (==(%(x, 2), 0) 1
else +(1, f(+(x, 1)))
您现在可以发现为什么在第二个示例中,最后一次调用没有到达f
吗?