我正在学习Scheme / Racket,并且对递归与迭代概念感到困惑。
具体问题是:编写一个总结数字列表的函数。
请使用以下代码,例如:
(define (func list)
(define (dostuff list res)
(if (empty? list)
res
(dostuff (cdr list) (+ (car list) res))))
(dostuff list 0))
根据教师的说法,这是一个迭代的解决方案。但我不明白为什么。 dostuff
在其实现中调用自身,所以不会自动使其递归吗?
或者我在这里错过了一些概念?
答案 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。