线性递归与线性迭代过程(LISP)

时间:2019-10-01 09:02:45

标签: recursion iteration lisp

因此,我正在阅读使用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

即使我确实了解了它们之间的主要区别,但我仍然不明白这一段

“这两个过程之间的对比可以用另一种方式看到。在迭代的情况下,程序 变量可随时提供有关流程状态的完整描述。如果我们停止了 在步骤之间进行计算,恢复计算所需要做的就是提供解释器 与三个程序变量的值。递归过程并非如此。在这种情况下, 由解释器维护但未包含在程序变量中的其他“隐藏”信息, 这表明在谈判延期运营链时的``流程在哪里''。越长 链,必须维护更多信息。

1 个答案:

答案 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

最终您将获得与使用循环结构相同的行为。但是请注意,即使使用循环,您也可以从此技巧中受益:消除尾部调用不仅限于递归调用,只要您可以在调用函数时安全地重用框架,就可以完成此操作。