因此,我正在阅读使用Lisp语言解释核心概念的SCIP书,而我目前仍停留在线性递归与线性迭代过程的差异上。
和用于演示差异的示例是n的计算!
线性递归过程
(if (= n 1)
1
(* n (factorial (- n 1)))))
它产生了这个过程:
(* 6 (factorial 5))
(* 6 ( * 5 (factorial 4)))
(* 6 ( * 5 ( * 4 ( factorial 3))))
(* 6 ( * 5 ( * 4 ( * 3 ( factorial 2)))))
(* 6 ( * 5 ( * 4 ( * 3 (*2 (factorial1))))))
(* 6 ( * 5 ( * 4 ( * 3 (*2 1)))))
(* 6 ( * 5 ( * 4 ( * 3 2))))
(* 6 ( * 5 ( * 4 6)))
(* 6 ( * 5 24))
(* 6 120)
720
线性迭代过程
(define (factorial n)
(if (= n 1)
1
(* n (factorial (- n 1)))))
产生以下过程
(Factorial 6)
(Fact-tier 1 1 6)
(Fact-tier 1 2 6)
(Fact-tier 2 3 6)
(Fact-tier 6 4 6)
(Fact-tier 24 5 6)
(Fact-tier 120 5 6)
(Fact-tier 720 6 6)
720
即使我确实了解了它们之间的主要区别,但我仍然不明白这一段
“这两个过程之间的对比可以用另一种方式看到。在迭代的情况下,程序 变量可随时提供有关流程状态的完整描述。如果我们停止了 在步骤之间进行计算,恢复计算所需要做的就是提供解释器 与三个程序变量的值。递归过程并非如此。在这种情况下, 由解释器维护但未包含在程序变量中的其他“隐藏”信息, 这表明在谈判延期运营链时的``流程在哪里''。越长 链,必须维护更多信息。
答案 0 :(得分:2)
递归版本具有以下跟踪:
0: (FACTORIAL 10)
1: (FACTORIAL 9)
2: (FACTORIAL 8)
3: (FACTORIAL 7)
4: (FACTORIAL 6)
5: (FACTORIAL 5)
6: (FACTORIAL 4)
7: (FACTORIAL 3)
8: (FACTORIAL 2)
9: (FACTORIAL 1)
9: FACTORIAL returned 1
8: FACTORIAL returned 2
7: FACTORIAL returned 6
6: FACTORIAL returned 24
5: FACTORIAL returned 120
4: FACTORIAL returned 720
3: FACTORIAL returned 5040
2: FACTORIAL returned 40320
1: FACTORIAL returned 362880
0: FACTORIAL returned 3628800
递归调用的中间结果用于产生新结果。在进行递归调用时,需要有一些内存来存储中间结果,以便将它们组合起来并在完成时计算结果。此存储位于调用堆栈中的stack frames中。
“迭代”过程具有以下踪迹:
0: (FACTORIAL 1 1 10)
1: (FACTORIAL 1 2 10)
2: (FACTORIAL 2 3 10)
3: (FACTORIAL 6 4 10)
4: (FACTORIAL 24 5 10)
5: (FACTORIAL 120 6 10)
6: (FACTORIAL 720 7 10)
7: (FACTORIAL 5040 8 10)
8: (FACTORIAL 40320 9 10)
9: (FACTORIAL 362880 10 10)
10: (FACTORIAL 3628800 11 10)
10: FACTORIAL returned 3628800
9: FACTORIAL returned 3628800
8: FACTORIAL returned 3628800
7: FACTORIAL returned 3628800
6: FACTORIAL returned 3628800
5: FACTORIAL returned 3628800
4: FACTORIAL returned 3628800
3: FACTORIAL returned 3628800
2: FACTORIAL returned 3628800
1: FACTORIAL returned 3628800
0: FACTORIAL returned 3628800
对于迭代过程,您可以看到结果始终是相同的:中间结果只是原样传递回顶层。换句话说,当我们处于最内层调用时,我们已经知道了最终结果。任何中间值都不需要存储,因为所有内容都保留在函数参数中。您也可以使用新的值和循环对参数进行变异。
实际上,这基本上是优化尾位置的调用时会发生的情况:递归调用重用与其调用者相同的堆栈帧,从而按以下方式展平跟踪:
(FACTORIAL 1 1 10)
(FACTORIAL 1 2 10)
(FACTORIAL 2 3 10)
(FACTORIAL 6 4 10)
(FACTORIAL 24 5 10)
(FACTORIAL 120 6 10)
(FACTORIAL 720 7 10)
(FACTORIAL 5040 8 10)
(FACTORIAL 40320 9 10)
(FACTORIAL 362880 10 10)
(FACTORIAL 3628800 11 10)
FACTORIAL returned 3628800
最终您将获得与使用循环结构相同的行为。但是请注意,即使使用循环,您也可以从此技巧中受益:消除尾部调用不仅限于递归调用,只要您可以在调用函数时安全地重用框架,就可以完成此操作。