【问题标题】:Space and time complexity of lisp functionslisp 函数的空间和时间复杂度
【发布时间】:2018-06-27 08:25:42
【问题描述】:

这里有两个lisp的函数

(defun fact (x &optional (acc 1))
  (if (zerop x) acc
      (fatt (- 1 x) (* x acc))))

(defun fatt (x)
  (if (zerop x) 1
      (* x (fatt (- x 1)))))

我怎样才能找到这个函数的空间和时间复杂度?

【问题讨论】:

  • 您的代码中很可能有 2 个拼写错误:您可能想从 fact 调用 fact 而不是 fatt,我认为您混合了减法参数顺序,所以表达式应该阅读(fact (- x 1) (* x acc))

标签: algorithm lisp time-complexity space-complexity


【解决方案1】:

第一个函数的(修正版)

(defun fact (x &optional (acc 1))
  (if (zerop x) 
      acc
      (fact (- x 1) (* x acc))))

当使用(fact N) 调用时,时间复杂度为 O(N),因为每个递归级别的步骤相同,并且有 N 次递归调用。空间复杂度取决于编译器。

每个像样的 LISP 编译器都会进行尾递归优化,因此 fact 的递归调用被替换为“跳转”到 fact 函数的开头,参数被新参数替换。所以你只有一组变量xacc,意思是O(1)。

当然,对于一个愚蠢的编译器,你最终会得到 N 个调用堆栈帧,每个都有自己的一组变量 xacc,意思是 O(N)。

第二个功能

(defun fatt (x)
  (if (zerop x) 
      1
      (* x (fatt (- x 1)))))

不允许(通常 - 也许周围有一些非常聪明的编译器......)允许尾递归优化,因此,当使用 (fatt N) 调用时,您最终的时间和空间都是 O(N) .

吹毛求疵

如果您使用较大的 N 值,则计算将不再适合 fixnum 数字范围,而是使用 bignums,然后违反所有递归调用执行相同步骤的假设。实际上,诸如乘法和减法之类的执行时间会随着数字的长度而增加。对于适合计算机内存的阶乘,每一步可能都是 O(log(x)) 而不是 O(1)。所以对于大数,我们将观察 O(NlogN) 而不是 O(N)。

【讨论】:

  • 在第一个函数中,我们有 N 个递归调用,但 N = x?
  • 我没有明确提到。我隐含地从顶级调用 (fact N) 开始,因此 N 成为第一个 x 值。我不会像 O(x) 那样表达它,因为在递归执行期间,我们会遇到很多不同的 x 值,因此可能不清楚是哪个 x 值。我也会编辑我的答案。
  • 由于 CL 标准不需要 TCO,因此您不能假设尾递归版本将是 O(1) 空间。在某些实现中会是 O(N),您需要使用 loop 宏或其他循环机制在 O(1) 空间中解决此问题。
  • @Sylwester 我一直在谈论“体面”和“愚蠢”的编译器,当然 CL 标准不需要尾递归优化——这超出了语言标准的范围。但是对于 CL,你的编译器很有可能做到这一点。
  • 我认为符合标准的 CL 是“体面的”。特定实现具有 TCO 在某些情况下(通常在编译时,通常不是在解释时),那么您可以说在实现 X 中编译时它是 O(1),但在 Common Lisp 中它仍然是 O(N),因为您无法保证TCO,它并不愚蠢。如果有人会制作 TCO Python,您仍然不能说尾调用已优化。如果标准有 TCO 要求,您可以。没有 TCO 的方案不是适当的方案实施。在运行和破坏堆栈时,用户没有选择标准的兼容 imp 是用户的错
猜你喜欢
  • 2016-12-20
  • 2019-04-21
  • 2012-08-14
  • 1970-01-01
  • 1970-01-01
  • 2013-09-12
  • 2018-02-10
  • 2019-03-02
  • 2018-07-23
相关资源
最近更新 更多