为什么在其体内调用自身的函数不被认为是递归的(vs迭代)?

时间:2016-03-03 02:37:44

标签: scheme racket

我正在学习Scheme / Racket,并且对递归与迭代概念感到困惑。

具体问题是:编写一个总结数字列表的函数。

请使用以下代码,例如:

(define (func list)
  (define (dostuff list res)
    (if (empty? list)
        res
        (dostuff (cdr list) (+ (car list) res))))
  (dostuff list 0))

根据教师的说法,这是一个迭代的解决方案。但我不明白为什么。 dostuff在其实现中调用自身,所以不会自动使其递归吗?

或者我在这里错过了一些概念?

2 个答案:

答案 0 :(得分:4)

我推荐MIT 6.001 lecture 1B of Structure and Interpretations,其中Gerald Jay Sussman教授对iteration vs recursion使用Scheme(LISP-1)的差异做出了优雅的解释。

这类算法背后的最大区别是与内存 space 的概念相关联。迭代过程不需要知道先前语句中发生的事情。但是,递归程序是。

稍微考虑一下这些算法的添加:

;; iterative
(define (+ x y)
    (if (= x 0)
         y
        (+ (1- x) (1+ y))))

;; recursive
(define (+ x y)
    (if (= x 0)
         y
         (1+ (+ (1- x) y))))

所有这些都做同样的事情,但执行程序的方式是不同的。

如果扩展(+ 3 4)的第一个执行,我们就有了这个流程:

(+ 3 4)
(+ 2 5)
(+ 1 6)
(+ 0 7)
7

时间= O(n),空格= O(1)

然而,对于第二个,请参阅:

(+ 3 4)
(1+ (+ 2 4))
(1+ (1+ (+ 1 4)))
(1+ (1+ (1+ (+ 0 4))))
(1+ (1+ (1+ 4)))
(1+ (1+ 5))
(1+ 6)
7 

时间= O(n),空格= O(n)

答案 1 :(得分:3)

它是“迭代的”,因为它是尾递归的。也就是说,它仅在尾部位置递归(即,代码在递归之后立即返回到调用者,没有其他工作,因此用递归调用替换当前调用帧是安全的。)

在像Scheme这样强制执行“正确的尾调用”的语言中,尾递归实际上是一个goto。正如Alexis的评论所说,在Scheme中,循环是使用尾递归编写的,因为Scheme没有goto。