关于Clojure中二进制搜索树的问题(不可变结构)

时间:2013-11-25 22:41:25

标签: clojure binary-search-tree immutability

我在编写从树中删除节点的代码时遇到了问题。

给定BST和键值,找到树中的键并删除它。

所以这是我的想法, 首先,如果BST为零,则返回nil 如果BST只有一个根节点,那么也返回nil。

然后,如果BST中的密钥与给定密钥匹配,请检查该节点具有的叶数。如果节点根本没有子节点,则从第一个前任(根)重新创建一个bst到该节点的最后一个前导,并共享不是前一个节点的所有其余数据。

如果节点有一个孩子,则视为没有孩子的孩子,但只是将孩子添加到最后一个孩子。

对于节点有两个孩子,我必须找到一些没有任何子节点的节点来替换它们的位置。

编写代码时出现了困难的部分,我现在不知道如何重新创建和共享树的数据。

那么有人可以提供一些提示或线索吗?

2 个答案:

答案 0 :(得分:4)

在我看来,你几乎拥有它,但只是需要一些细节方面的帮助。因此,假设您有一些节点结构和以下函数来操作它:

  • (left-subtree [node]) - 返回node的左子树,如果nil没有左子树则返回node
  • (right-subtree [node]) - 返回node的正确子树,如果nil没有正确的子树,则返回node
  • (value [node]) - 返回与node
  • 相关联的值
  • (leaf? [node]) - 如果true是一片叶子,则返回node,否则false

现在让我们编写一个without-root函数,该函数接受(子)树并返回一个新树,其中包含原始树中除根节点外的所有内容:

(defn without-root [node]
  (cond (leaf? node) nil ; resulting tree is the empty tree, return nil
        (and (left-subtree node) ; two children, "difficult" case
             (right-subtree node)) (handle-difficult-case node)
        ;; cases for single child
        (left-subtree node) (left-subtree node)
        (right-subtree node) (right-subtree node)))

正如您在问题中所述,“困难”的情况是node有两个孩子。所以我决定把它拆分成一个单独的函数来促进讨论。

让我们谈谈handle-difficult-case。由于有两个孩子,我们不知何故需要将它们组合成一棵树。如果您阅读了维基百科关于BST Deletion的内容,您基本上想要采用有序前导或后继(即左子树的最右边节点,或右子树的最左边节点)并制作它是新根。选择哪一个都没关系 - 任何一个都可以。为了便于讨论,我们将选择左子树的最右边节点。

现在我们可以编写一个新函数without-rightmost-node,它接受​​一个树并返回一个没有最右边节点的新树。但是,我们还需要存储在该节点中的值。所以我们要么需要独立调用一些find-rightmost-node函数来获取它的值(效率很低),要么返回值和新树(这会混淆函数的用途)。

相反,让我们编写一个接受树的函数,并返回一个等同于原始树的新树,除了它的根是原始树的最右边节点。为了好玩,我们可以调用这个函数percolate-rightmost-node,因为正如我们所看到的,最右边的节点会递归地“冒泡”到(子)树的顶部。

(defn percolate-rightmost-node [node]
  (if-let [right-side (right-subtree node)]
    ;; then (recurse down the right side)
    (let [percolated (percolate-rightmost-node right-side)]
      ;; Construct a new tree from the result.
      (with-left-subtree percolated
                         (with-right-subtree node (left-subtree percolated))))
    ;; else (we are at the rightmost node -- return it!)
    node))

我觉得if-let表达的“然后”方面不是很清楚,所以让我详细说明一下。基本上,我们采用percolated子树,将其保留为子树(这是percolated的唯一子项),并将其替换为node的右子树。然后,我们将结果替换为percolated的左子树(有效地重新生根树),生成最终结果。

percolate-rightmost-node的输出只有一个左子树 - 它永远不会有一个正确的子树。所以在结果“冒泡”之后,我们只需要给它一个正确的子树。因此,我们可以将handle-difficult-case实现为:

(defn handle-difficult-case [node]
  (let [percolated (-> node           ; Find the rightmost node of the left
                       (left-subtree) ; subtree and "percolate" it to the top
                       (percolate-rightmost-node))]
    ;; Now take the percolated tree.  Its right subtree is empty,
    ;; so substitute in the right subtree of node.
    (with-right-subtree percolated
                        (right-subtree node))))

那应该是它。当然,您需要根据代码进行调整(至少,内联handle-difficult-case或给它一个合适的名称)。但希望这能让你开始。

警告:我没有尝试测试此答案中给出的代码。欢迎更正!

答案 1 :(得分:2)

这将是一个很长的答案,所以请让我提前道歉,指出你的书,而不是直接回答。我强烈建议您查看作为PDF格式的Purely Functional Data Structures(法律上)。虽然这本书在print/ebook中也是一本好书。

并且超短的回答是使用Clojure内置的sorted-map如果你想在实践中这样做(虽然写你自己的意志当然得到书呆子街道信用)因为有序地图使用二进制红黑树引擎盖下