Tree 如何用指定索引处的另一棵树替换树的一部分?
假设我有两棵树:Tree 如何用指定索引处的另一棵树替换树的一部分?,tree,scheme,racket,Tree,Scheme,Racket,假设我有两棵树: 树A-”(+(*56)(sqrt 3)): 树B-”(-42): 目标:在指定的树A索引位置用树B替换树A的子树之一。索引位置从根节点的0开始,并且是深度优先。在上面树A的图中,我用它们的索引标记了所有节点,以显示这一点 例如,(replace subtree treeA 4 treeB)将树A中索引4处的子树替换为树B,从而生成树(+(*5 6)(-4 2)): 如何实现(替换子树树a索引树b) 这个问题与我的另一个问题有些关联:。我在解决这个问题上有很大的困
- 树A-
:”(+(*56)(sqrt 3))
- 树B-
:”(-42)
(replace subtree treeA 4 treeB)
将树A中索引4处的子树替换为树B,从而生成树(+(*5 6)(-4 2))
:
如何实现(替换子树树a索引树b)
这个问题与我的另一个问题有些关联:。我在解决这个问题上有很大的困难,但我最终通过使用连续传递方式(CPS)找到了一个可行的解决方案。然而,这个问题似乎要困难得多。我完全不知道该如何开始!实现和线索是最受欢迎的。我对不使用
call/cc
的实现特别感兴趣
编辑 在等待答案时,我想出了一个权宜之计。它依赖于
set代码>,我不喜欢
(define (replace-subtree tree index replacement)
(define counter 0)
(define replaced #f) ; Whether or not something has been replaced.
(define (out-of-bounds-error)
(error "Index out of bounds" index))
(define (traverse-tree tree)
(cond [(null? tree)
(error "Invalid tree: ()")]
[(= counter index)
(set! counter (+ counter 1))
(set! replaced #t)
replacement]
[(pair? tree)
(set! counter (+ counter 1))
(cons (car tree)
(traverse-children (cdr tree)))]
[else
;; Possible only during the initial call to traverse-tree.
;; e.g. (replace-subtree 'not-a-list 9999 '(+ 1 2)) -> error.
(out-of-bounds-error)]))
(define (traverse-children children)
(cond [(null? children) '()]
[(list? (car children))
;; list? instead of pair? to let traverse-tree handle invalid tree ().
(cons (traverse-tree (car children))
(traverse-children (cdr children)))]
[(= counter index)
(set! counter (+ counter 1))
(set! replaced #t)
(cons replacement
(traverse-children (cdr children)))]
[else
(set! counter (+ counter 1))
(cons (car children)
(traverse-children (cdr children)))]))
(let ([result (traverse-tree tree)])
(if replaced
result
(out-of-bounds-error))))
这是一个比我想象的更难的问题。这很困难的一个原因是,你称之为“树”的东西实际上不是树:它们是DAG(有向无环图),因为它们可以共享子树。简单地说,这只发生在叶节点上:在(abb)
中,索引为1和2的节点是eq?
:它们是相同的对象。但事实上,它可以发生在任何节点上:给定
(define not-a-tree
(let ([subtree '(x y)])
(list 'root subtree subtree)))
索引为1和2的节点是同一对象,不是叶节点:这是DAG,不是树
这很重要,因为它破坏了一个显而易见的方法:
找到你感兴趣的索引节点李>
使用节点上的eq?
,遍历构建新树的树,直到找到此节点,然后将其替换
您可以看到,如果我想用(x y y)
中的索引2替换节点,那么这将失败:它将用索引1替换节点
一种可能是最简单的方法是将这些“树”转化为节点具有标识的树。然后对这些树执行上述替换,然后将它们转换回原始表示。然而,这可能会丢失一些重要的结构:例如,上面的对象将从DAG变成树。这在实践中不太重要
因此,要做到这一点,你需要一个函数来获取老树,将它们转换为具有适当唯一性的新树,然后将它们转换回来。这几乎可以肯定是概念上最简单的方法,但我太懒了,没有写出所有这些代码
所以,这里有一个答案,不是那种方法。相反,它所做的是遍历树,跟踪节点索引,并在需要时构建新树。要做到这一点,进入节点的东西需要返回两个东西:一个节点(可能是新创建的节点,即替换节点或传递的原始节点)和索引的新值。这是通过从walker返回两个值来完成的,并且有相当数量的头发在执行此操作
这也没有尝试使用Racket的一些小子集:它使用多个值,包括语法(let values
),这使得它们使用起来不那么痛苦,并且还使用for/fold
来完成大部分工作,包括折叠多个值。所以,你需要了解这些事情,看看它能做什么。(这也可能意味着它不适合作为家庭作业的答案。)
有一点值得注意的是,walker有点作弊:一旦完成了替换,它甚至都不会尝试正确地计算索引:它只知道它比它所关心的要大,然后逃之夭夭
首先,这里是处理树的抽象:请注意,makenode
与前一个问题的答案中的makenode
并不完全相同:它现在需要一个孩子列表,这是一个更有用的签名
(define (make-node value children)
;; make a tree node with value and children
(if (null? children)
value
(cons value children)))
(define (node-value node)
;; the value of a node
(cond
[(cons? node)
(car node)]
[else
node]))
(define (node-children node)
;; the children of a node as a list.
(cond
[(cons? node)
(cdr node)]
[else
'()]))
现在,这里是做这项工作的函数
(define (replace-indexed-subtree tree index replacement)
;; Replace the subtree of tree with index by replacement.
;; If index is greater than the largest index in the tree
;; no replacemwnt will happen but this is not an error.
(define (walk/indexed node idx)
;; Walk a node with idx.
;; if idx is less than or equal to index it is the index
;; of the node. If it is greater than index then we're not
;; keeping count any more (as were no longer walking into the node).
;; Return two values: a node and a new index.
(cond
[(< idx index)
;; I still haven't found what I'm looking for (sorry)
;; so walk into the node keeping track of the index.
;; This is just a bit fiddly.
(for/fold ([children '()]
[i (+ idx 1)]
#:result (values (if (< i index)
node
(make-node (node-value node)
(reverse children)))
i))
([child (in-list (node-children node))])
(let-values ([(c j) (walk/indexed child i)])
(values (cons c children) j)))]
[(= idx index)
;; I have found what I'm looking for: return the replacement
;; node and a number greater than index
(values replacement (+ idx 1))]
[else
;; idx is greater than index: nothing to do
(values node idx)]))
;; Just return the new tree (this is (nth-value 0 ...)).
(let-values ([(new-tree next-index)
(walk/indexed tree 0)])
new-tree))
在walk/index
的顶部放置一个合适的printf
,这样你就可以看到它在树上行走时做了什么。是((lambda(a)(lambda(b)(+a(sqrt b)))1)4)
树吗?它的汽车不也是一棵树吗?为什么忽视它?我认为它不应该被忽略。@WillNess'((lambda)(lambda(b)(+a(sqrt b)))1)4
是一个具有根节点的树。((lambda(a)(lambda(b)(+a(sqrt b)))1
和一个子节点4
。在我的树实现中,列表的car
是非终端节点的“内容”,列表的cdr
包含其子节点。如果您的新实现工作正常,并且您希望对其进行审阅,那么是否可以将其发布到CodeReview?(不过那里的交通量很低…)。答案中的代码在AFAIAC中做了正确的事情,我也会做同样的事情,当然取决于语法。或者我们可以先运行上一个问题中的索引,将每个节点的有效负载与其索引配对。我认为@tfb在最初的讨论中暗示了这一点。然后,在这种索引树上,替换代码将在日志时间内直接到达所需节点,而不必像它那样在线性时间内搜索整个树。索引本身当然是线性的,即树中节点和叶子的数量是线性的。同样,我认为这只适用于dfs索引,而不适用于bfs索引。谢谢您的回答。请给我几天时间运行、调整并理解您的答案,然后再投票/接受它。(在阅读了您答案的第一部分后)reeq?> (replace-indexed-subtree '(+ (* 5 6) (sqrt 3)) 4 '(- 4 2))
'(+ (* 5 6) (- 4 2))
> (replace-indexed-subtree '(+ (* 5 6) (sqrt 3)) 0 '(- 4 2))
'(- 4 2)
> (replace-indexed-subtree '(+ (* 5 6) (sqrt 3)) 20 '(- 4 2))
'(+ (* 5 6) (sqrt 3))