scheme - 函数tail-recursive?

时间:2015-09-14 17:41:46

标签: recursion scheme tail-recursion

我在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是递归的。但我无法回答为什么,这有点让我感到恼火。我认为跟踪循环可以帮助我,但我不太确定它的含义以及它对我的帮助。我试着比较所有三个函数找到相似之处,以及它们彼此之间的差异。但是,不幸的是,我无法做到这一点......

希望有人友好可以帮助我,谢谢。哦,我也是计算机科学的新手,所以也许我会使用一些错误的术语。如果有什么不清楚,只需说出来。 :)

3 个答案:

答案 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中进行了优化。

现在,你的两个程序中哪一个是尾递归的?