我在scheme中实现了这个递归函数:
(define f (lambda (n)
(cond ((= n 0) 0)
((= n 1) 2)
((= n 2) 37)
((odd? n) (+ (f (- n 3)) 1))
(else (+ (f (- (/ n 2) 1)) 7))))
)
在很多练习中,我被问到我的解决方案是否是尾递归,说实话,无论我多少次阅读尾递归的定义,我都不明白。如果我要定义tail-recursive,那就是这样的。
定义:无论输入值如何,都会在恒定的存储空间中计算尾部反复函数。
这里还有另外两个。其中一个是尾递归,我想找出哪一个。
(define ln2-a
(lambda (n)
(define loop
(lambda (n)
(if (= n 1)
0
(+ 1 (loop (quotient n 2))))))
(trace loop)
(loop n)))
在这里。
(define ln2-b
(lambda (n)
(define loop
(lambda (n result)
(if (= n 1)
result
(loop (quotient n 2) (+ result 1)))))
(trace loop)
(loop n 0)))
让我们来看看最后两个函数。在这里,我想ln2-b是递归的。但我无法回答为什么,这有点让我感到恼火。我认为跟踪循环可以帮助我,但我不太确定它的含义以及它对我的帮助。我试着比较所有三个函数找到相似之处,以及它们彼此之间的差异。但是,不幸的是,我无法做到这一点......
希望有人友好可以帮助我,谢谢。哦,我也是计算机科学的新手,所以也许我会使用一些错误的术语。如果有什么不清楚,只需说出来。 :)
答案 0 :(得分:3)
在您的第一个代码块中, f 不是尾递归。必须发生对 f 的递归调用,返回其结果,然后必须将1或7(取决于分支)添加到结果中。由于对 f 的递归调用的调用必须返回,并且对 f 进行任意多次递归调用,这意味着必须分配任意多个堆栈帧。
当你想到调用一个函数时,可能会想象你拿一张新纸并在那张纸上写下所有局部变量和值。当您确定函数的结果时,可能会对同一函数进行递归调用。如果电话是一个尾部电话,那么当你为它拿一张纸时,可以扔掉旧表,因为你不再需要它的任何值。< / p>
例如,考虑两种计算列表长度的方法:
(define (length-1 list)
(if (null? list)
0
(+ 1
(length-1 (cdr list))))) ; recursive call, NOT a tail-call
在此实现中,您必须完成对 length-1 的递归调用,获取其结果,并向其添加1。这意味着你需要在递归调用之后回到某个地方。现在考虑一个尾递归版本:
(define (length-2 list current-length)
(if (null? list)
current-length
(length-2 (cdr list) ; recursive call, IS a tail-call
(+ 1 current-length))))
在此版本中,一旦您开始对 length-2 进行递归调用,您就不再需要原始调用中的任何上下文了。实际上,您可以通过将 (cdr list)分配到列表,然后分配 <将其转换为循环strong>(+ 1当前长度)到当前长度。您可以重用相同的堆栈空间。这就是尾调用优化(当尾调用是同一个函数时)等同于循环。
答案 1 :(得分:0)
尾递归函数会将累加的结果传递给每个调用,这样一旦达到结束条件,结果就可以在函数的最后一次调用时立即返回。
非尾递归函数需要在返回之后对结果进行处理。因此,必须记住每一层递归,以便它可以恢复计算最终结果。
在你的第一个例子中,你要添加f的下一次调用的结果,所以它不是尾递归。
第二个例子也是添加到下一个循环调用,所以它不是尾递归。
第三个例子,将最终结果作为参数传递给下一个函数调用,因此它是尾递归的。
答案 2 :(得分:0)
这很简单..当你需要对递归的结果做一些事情时,不尾递归。
(define (length lst)
(if (null? lst)
0
(+ 1
(length (cdr lst))))
在这里,您清楚地看到我们必须+
1来自递归的答案。这种情况在每一步都会发生,因此堆栈会一直存在,直到基本案例命中为止,我们会在回程中为每个元素添加1
。 (length '(1 2 3 4)) ; ==> (+ 1 (+ 1 (+ 1 (+ 1 0))))
当过程结束并且结果是最后一步(基本情况)的结果时,它是尾递归的。
(define (length lst)
(define (aux lst count)
(if (null? lst)
count ; last step
(aux (cdr lst) (+ 1 count))))
(aux lst 0))
这里的帮助程序有count
作为参数,而不是必须等待加1才能在递归发生之前(通过增加参数)。整个事情的结果只是基本情况的结果而已。它是尾递归的。甚至对助手的调用都是尾调用,但不是递归调用,但所有尾调用都在Scheme中进行了优化。
现在,你的两个程序中哪一个是尾递归的?