【问题标题】:Problems about Binary Search Tree in Clojure (immutable structure)Clojure中关于二叉搜索树的问题(不可变结构)
【发布时间】:2013-12-10 21:05:46
【问题描述】:

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

给定一个 BST 和一个键值,在树中找到键并将其删除。

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

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

如果节点有一个子节点,则将其视为没有子节点的节点,但只需在最后一个前驱节点上添加一个子节点。

因为节点有两个孩子,我必须找到一些没有孩子的节点来替换他们的位置。

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

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

【问题讨论】:

  • 你考虑过拉链吗?
  • @lgrapenthin,你的意思是用拉链来存储信息?
  • 老实说,我完全不知道如何在共享数据的同时重新创建树。 @lgrapenthin
  • Zippers 是一种对不可变树结构执行操作的方法。它们提供了一种以更类似于命令式编程的风格来表达操作的方法,同时保持纯粹的功能性。它们不是必需的,但它是你腰带上的一个很好的工具。您可以通过 clojure.zip 命名空间使用拉链:clojure.github.io/clojure/clojure.zip-api.html

标签: clojure binary-search-tree immutability


【解决方案1】:

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

  • (left-subtree [node]) - 返回node 的左子树,如果node 没有左子树,则返回nil
  • (right-subtree [node]) - 返回node 的右子树,如果node 没有右子树,则返回nil
  • (value [node]) - 返回与 node 关联的值
  • (leaf? [node]) - 如果node 是叶子,则返回true,否则返回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。由于有两个孩子,我们需要以某种方式将它们组合成一棵树。如果您阅读了 Wikipedia 关于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 表达式的“then”一侧不是很清楚,所以让我详细说明一下。基本上,我们采用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 或给它一个合适的名称)。但希望这能让你开始。

告诫购买者:我没有尝试测试此答案中给出的代码。欢迎指正!

【讨论】:

    【解决方案2】:

    这将是一个很长的答案,所以请让我提前道歉,因为我将您指向一本书而不是直接回答。我强烈建议您查看Purely Functional Data Structures,它(合法)可从作者那里以 PDF 格式获得。虽然在print/ebook 中拥有一本好书。

    如果你想在实践中使用 Clojure 内置的 sorted-map(虽然你自己写当然会得到 nerd-street-cred),那么超级简短的答案是使用 Clojure 内置的,因为排序的地图在引擎盖

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-19
      • 1970-01-01
      • 2021-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多