【问题标题】:Order of growth in mixed functions混合函数的增长顺序
【发布时间】:2017-10-07 11:26:42
【问题描述】:

SICP 1.3.1 节的求和过程产生了一个N 阶空间和时间复杂度的线性递归过程。这个过程的代码是:

(define (sum-integers a b)
    (if (< a b)
        0
        (+ a (sum-integers  (+ a 1) b))))

我想知道的是,如果我决定使用类似的过程对一系列斐​​波那契数进行求和:

(define (sum-fib a b)
    (if (< a b)
        0
        (+ (fib a) (sum-fib (+ a 1) b)))) 

将fib定义为:

(define (fib n)
  (cond ((= n 0) 0)
        ((= n 1) 1)
        (else (+ (fib (- n 1))
                 (fib (- n 2))))))

如何分析 sum-fib 的空间和时间复杂度?我会忽略整个过程的线性递归风格,并将其中 fib 的树递归作为最坏的情况优先考虑吗?我是否必须以某种方式结合 fib 和 sum-fib 的空间/时间复杂性,如果是,如何?另外,假设我从另一个程序员那里得到了 sum-fib,并且我将它用作更大系统中的组件。如果我的程序因为 fib 的实现方式而变慢,我怎么知道?

这是我在这个平台上的第一个问题,所以还请就如何更好地发布和找到问题的答案提出建议。感谢您的贡献。

【问题讨论】:

标签: algorithm recursion scheme racket sicp


【解决方案1】:

您的代码中有一个轻微错误。检查 SICP 后,我假设您打算在 sum-integerssum-fib 中使用 &gt; 而不是 &lt;。这是我做的唯一修改,如有错误请指正。

注意:我没有正式的背景,但是这个问题已经有很长一段时间没有答案了,所以我想我会与遇到它的其他人分享我的想法。

时间

在处理时间复杂度时,我们关心随着n 的增大而执行了多少次迭代。在这里,我们可以假设nsum-fibab(含)之间的距离。在这种情况下,函数sum-fib 本身只会递归n 次。如果a 为 0,b 为 9,则函数将运行 10 次。这是完全线性的,或者说 O(n),但并不是那么简单:下一个要问的问题是每次迭代会发生什么?

我们知道求和部分是线性的,所以剩下的就是斐波那契函数。在内部,您会看到它要么立即终止( O(1) ),要么分支为对自身的两个递归调用。 Big-O 表示法关注最坏情况,即分支。我们将 1 次呼叫转为 2,然后转为 4,然后转为 8,依此类推,n 次。这种行为是 O(2^n)。

不要忘记,这被称为 n 次作为总体 O(n) 求和循环的一部分,因此总函数将为 O(n(2^n))。

空格

函数的空间要求有点不同。通过手写写出正在发生的事情,您可以开始看到函数形式的形状。这就是早期在 SICP 中展示的内容,其中“金字塔”函数与线性函数进行了比较。

要记住的一点是,Scheme 是尾调用优化的。这意味着,如果递归调用位于函数的末尾(意味着在递归调用之后没有发生任何指令),则可以重用帧,并且不需要额外的空间。例如:

(define (loop n)
    (if (> n 2)
    0
    (loop (+ n 1))))

抽出(loop 0) 将是:

(loop 0)
(loop 1)
(loop 2)
0

您可以看到所需的空间是线性的。比较一下:

(define (loop n)
    (if (> n 2)
    0
    (+ n (loop (+ n 1)))))

(loop 0):

(loop 0)
(1 + (loop 1))
(1 + (2 + (loop 2)))
(1 + (2 + 0))
(1 + 2)
3

您可以看到,在这种情况下,所需的空间随着所需迭代次数的增加而增加。

在您的情况下,所需的空间将随着n 的增加而急剧增加,因为fib 为每个数字生成一个完整的树,并且不是尾递归的,sum-fib 也不是。

我怀疑所需的空间也将是 O(n(2^n))。 sum-fib 函数(忽略 fib 调用)在空间上似乎是线性的,或者 O(n)。它每次迭代调用 2 fibs。每个fib 再分支成 2 个,并且不是尾递归的,因此所需空间为 O(2^n)。结合它们,我们得到 O(n(2^n))。是否会一直如此,我不确定。

如何测试慢功能

您正在寻找的东西称为分析器。它会在运行时监视您的代码,并向您报告哪些函数花费的时间最多、哪些函数被最常调用等信息。对于 Scheme,Dr. Racket 是一个具有内置分析器的 IDE。

忠告:首先让您的软件运行,然后再考虑分析和优化。许多程序员在没有先了解真正的瓶颈所在的情况下就陷入了对代码的超优化。事实证明,5 分钟的调整可以让您获得 50% 的提升,但您可以花费数周时间使用神秘算法获得 1% 的性能提升。

【讨论】:

  • 感谢您详细而贴切的解释;阅读您的回答增强了我对流程如何演变的直觉,我很欣赏您如何很好地解释如何结合对程序不同部分的分析以全面了解正在发生的事情。我必须抽出一些时间来阅读我的 Racket 文档以了解分析器的工作原理,感谢您让我意识到这一点。随着我作为一名程序员的成熟,我会牢记您的建议,到目前为止,我所有的编码都仅限于教科书练习和示例,但我迫不及待地想要构建一些毛茸茸的东西。再次感谢。
  • 完全没问题,很高兴能帮上忙。请记住,Racket 是 Scheme 的(经过大量修改的)方言。 Dr. Racket IDE 不仅限于运行 Racket 代码,它还可以通过更改其语言设置来运行 R5RS Scheme,这很可能是您现在想要的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多