【问题标题】:Logarithmic Time Scheme Algorithm对数时间方案算法
【发布时间】:2014-09-03 01:18:39
【问题描述】:

我目前正在阅读由 Hailperin、Kaiser 和 Knight 撰写的 Scheme 书籍“具体抽象”。我已经编写了相同算法的递归和迭代版本,如下所示。该算法用于对数量或事物执行 n 次操作。所以,一般来说,一起复制(操作n件事)。例如,我可以找到 3 个像这样的立方体,一起复制 (* 3 3) 或 3 个平方的一起复制 (* 2 3)。

递归版本:

(define together-copies-of-linrec
  (lambda (combine quantity thing)
    (define together-iter
      (lambda (combine start thing)
        (if (= start quantity)
            thing
            (combine (together-iter combine (+ start 1) thing)
                     thing))))
      (together-iter combine 1 thing)))

迭代版本:

(define together-copies-of-linit
  (lambda (combine quantity thing)
    (define together-iter
      (lambda (combine start newthing)
        (if (= start quantity)
            newthing
            (together-iter combine (+ start 1) (combine newthing thing)))))
    (together-iter combine 1 thing)))

现在,我需要编写这个算法的对数时间版本,但我真的不知道从哪里开始。在这种情况下,我看不出如何减少每个实例的操作以使其成为对数时间。

【问题讨论】:

    标签: algorithm scheme time-complexity


    【解决方案1】:

    假设combine 保证引用透明0,您可以通过将整个计算视为二叉树来编写它的对数时间版本1。例如,将调用(together-copies-of * 4 3) 想象为二叉树(将together-copies-of 缩写为t):

                   (t * 4 3)
                       |
                   ----*-----
                  /          \
                 /            \
           (t * 2 3)         (t * 2 3)
               |                 |
          -----*-             ---*----
         /       \           /        \
    (t * 1 3) (t * 1 3) (t * 1 3) (t * 1 3)
    

    这里的重点是(t * 1 3)不需要计算四次,(t * 2 3)也不需要计算两次;您只需要计算它们中的每一个。如果我们确保每行只计算一次,那么我们只需要每行执行 O(1) 次操作。由于二叉树中的行数与元素数成对数,这意味着我们有 O(log n) 算法的工作原理。

    相比之下,您当前的算法如下所示:

    (t * 4 3)
        |
      3 *
         \
      (t * 3 3)
           |
         3 *
            \
         (t * 2 3)
             |
           3 *
              \
           (t * 1 3)
              |
            3 * 3
    

    这就是使您的程序(两者)线性的原因:它的结构是一条大线,因此它必然需要线性时间。

    下面的简单程序实现了二叉树的思想。

    (define together-copies-of-log
      (lambda (combine quantity thing)
        (if (= quantity 1)
            thing
            (let ((child (together-copies-of-log combine (/ quantity 2) thing)))
              (combine child child)))))
    

    由于我只是为了演示这个概念而写得这么快,所以它有一些不足之处:

    1. 如果 quantity 不是 2 的幂,则会失败。
    2. 它不是尾递归的。

    修复这些问题留给读者作为练习。 :)


    澄清几点:

    0:为什么combine 需要引用透明?如果不是这样,那么将对combine 的两次调用更改为一次可能实际上会改变值,因此它不是一个有效的转换。

    1:为什么是二叉树,而不是三叉树或任何其他叉树?这是因为combine 正好有两个参数。如果您正在为不同数量的函数编写版本,那么树将具有相同的数量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多