理解尾递归2

时间:2014-05-09 07:51:07

标签: scheme tail-recursion letrec

最初我发布了一个问题" understanding a tail-recursive vector->list answer"这是其他问题。我对计划的一般理解真的很模糊。所以我现在还有几个问题:

;;;;;; original code ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define vector->list:rec
 (lambda (v)
  (letrec ((helper
      (lambda (vec r i)
        (if (< i 0) 
            r
            (helper vec (cons (vector-ref v i) r) (- i 1))  ;; Q1
            ))))
    (if (> (vector-length v) 0)  ;; line 9
      (helper v                  ;; line 10 
              (cons (vector-ref v (- (vector-length v) 1)) '()) 
              (- (vector-length v) 2))
      '()))))

Q2)尾递归让我很难理解。我理解为什么他们需要尾递归,基本上他们使用它来避免迭代,所以他们使用helper作为中间例程..所以他们可以避免把每个迭代放入堆栈....就像这样。和letrec / lambda表达式一样:

;;;;;;;;;letrec/lambda express;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (some-procedure...)
  (letrec ((helper (lambda (x) 
               ...
               (if some-test?
                   (helper ...))))) ; recursive call --- Q2-1
       ...
    (helper ...)  ; call to recursive local procedure  ---- Q2-2
  ...))

第Q2-2行:为什么这是本地递归&#39;? &#39;地方&#39;听起来对中间例程递归...这里中间意味着我的理解..

[我的困惑]尾递归不应该迭代(调用)本身直到整个程序结束 - 所以不应该在中间例程中。= shouldn&#39在帮助者里面?根据我的理解,直到现在......帮助器是用于封装在letrec表达式中的中间例程。?。)所以最后只调用自己。(我的意思是:在letrec ...之外?)。

1 个答案:

答案 0 :(得分:2)

首先,我会稍微改写一下你的例子:

(define (vector->list-iter v)  
  (let loop ((i (- (vector-length v) 1)) (acc '()))
    (if (< i 0)
        acc
        (loop (- i 1) (cons (vector-ref v i) acc)))))

要看到差异,我们可以制作非尾递归版本:

(define (vector->list-rec v)
  (define len (vector-length v))
  (let loop ((i 0))
    (if (>= i len)
        '()
        (cons (vector-ref v i) (loop (+ i 1))))))

Scheme中没有循环功能。它只是不会增加堆栈的递归和递归,因为在上一步中还有更多的事情要做,称为尾递归。

由于我们可以以任何方式迭代向量(它的O(1)访问),我们可以将它迭代到第一个或从头到尾迭代。由于列表只能在最后一次进行,因此非尾递归版本不会在第一个元素上应用cons,直到它创建了除第一个元素之外的整个列表。这使得5元素向量在击中基本情况时具有5个连续。如果它是一个大向量,它可能导致堆栈溢出。

第一个例子首先使列表包含最后一个元素,并在它完成时进行递归。它不需要包含任何东西,因为在递归之前完成了收益。并非所有可以像这样处理的问题。想象一下,你想要复制一个列表。它可以从头到尾迭代,但从头到尾构建。没有突变或额外的消耗,没有办法使这样的程序尾递归。