【问题标题】:SICP 2.64 order of growth of recursive procedureSICP 2.64 递归过程的增长顺序
【发布时间】:2015-02-02 09:11:51
【问题描述】:

我正在自学 SICP,很难找到递归函数的增长顺序。

以下过程 list->tree 将有序列表转换为平衡搜索树:

(define (list->tree elements) 
  (car (partial-tree elements (length elements)))) 

(define (partial-tree elts n) 
  (if (= n 0) 
      (cons '() elts) 
      (let ((left-size (quotient (- n 1) 2))) 
        (let ((left-result (partial-tree elts left-size))) 
          (let ((left-tree (car left-result)) 
                (non-left-elts (cdr left-result)) 
                (right-size (- n (+ left-size 1)))) 
            (let ((this-entry (car non-left-elts)) 
                  (right-result (partial-tree (cdr non-left-elts) 
                                              right-size))) 
              (let ((right-tree (car right-result)) 
                    (remaining-elts (cdr right-result))) 
                (cons (make-tree this-entry left-tree right-tree) 
                      remaining-elts))))))))

我一直在网上查看解决方案,我认为以下网站提供了最佳解决方案,但我无法理解它:

jots-jottings.blogspot.com/2011/12/sicp-exercise-264-constructing-balanced.html

我的理解是,程序“partial-tree”每次被调用时都会重复调用三个参数——分别是“this-entry”、“left-tree”和“right-tree”。 (并且只有在必要时才使用“remaining-elts”——无论是在第一次“partial-tree”调用中还是在调用“non-left-elts”时)

  1. this-entry 调用:car、cdr 和 cdr(left-result)
  2. left-entry 调用:car、cdr 和其自身的长度每一步减半
  3. 右入口调用:car,本身以 cdr(cdr(left-result)) 作为参数,长度减半

'left-entry' 将以 2 log(n) 为基数,并且所有三个参数都分别调用 'left-entry'。 因此它将具有三叉树状结构,并且我认为总步数类似于 3^log(n)。但解决方案说它只使用每个索引 1..n 一次。但是'this-entry'不是将每个节点上的相同索引减少为'right-entry'吗?

我很困惑.. 此外,在 (a) 部分,解决方案网站指出:

"在非终止情况下partial-tree首先计算个数 应该进入平衡二叉树左子树的元素 大小为 n 的树,然后使用元素调用部分树,并且 既产生这样的子树的值,又产生不的元素列表 在那个子树中。然后它将未使用元素的头部作为 当前节点的值"

我相信该程序在左树之前执行此条目。为什么我错了?

这是我关于 CS 的第一本书,我还没有接触过 Master Theorem。 在一些解决方案中提到了它,但希望我应该能够在不使用它的情况下解决这个问题。

感谢您的阅读,期待您的热情回复,

克里斯

【问题讨论】:

    标签: recursion tree scheme sicp


    【解决方案1】:

    您需要了解let 表单的工作原理。在

              (let ((left-tree (car left-result)) 
                    (non-left-elts (cdr left-result))
    

    left-tree“呼叫”任何东西。它被创建为一个新的词法变量,并赋值为(car left-result)。它周围的括号只是为了组合在一起描述let形式引入的一个变量的元素:变量的名称和它的值:

              (let (  (   left-tree      (car left-result)  ) 
              ;;      ^^                                   ^^
                      (   non-left-elts  (cdr left-result)  )
              ;;      ^^                                   ^^
    

    以下是理解如何递归过程工作的方法:不要

    只是不要试图理解它是如何工作的;而是分析它做了什么,假设它做了(对于较小的案例)它应该做的事情。

    这里,(partial-tree elts n)接收两个参数:元素列表(大概要放入树中)和列表的长度。它返回

                (cons (make-tree this-entry left-tree right-tree) 
                      remaining-elts)
    

    一棵树的 cons 对 - 如果长度参数正确,则转换的结果和剩余的元素,它们应该在最顶层的调用中,

    现在我们知道它应该做什么,我们来看看它。并且确实假设上述内容完全有意义:将元素数量减半,处理列表,取回树和剩余列表(现在非空),然后处理剩下的。

    this-entry 不是一棵树 - 它是一个位于树节点中的元素:

                (let ((this-entry (car non-left-elts)) 
    

    设置

                      (right-size (- n (+ left-size 1))
    

    表示n == right-size + 1 + left-size。这是进入节点本身的 1 个元素,this-entry 元素。

    并且由于每个元素直接进入其节点一次,因此该算法的总运行时间与输入列表中的元素数量成线性关系,并使用对数堆栈空间。

    【讨论】:

    • 您的解决方案并重新查看第 64 页的 let 表达式的定义就很清楚了。很高兴看到你的回答,谢谢。
    • (如果我之前没有说过的话)不客气。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-19
    • 2013-06-19
    • 1970-01-01
    • 2023-01-31
    • 2016-02-24
    • 1970-01-01
    相关资源
    最近更新 更多