【问题标题】:Delete tree leaf with specific Int in Haskell在 Haskell 中删除具有特定 Int 的树叶
【发布时间】:2013-06-01 19:22:39
【问题描述】:

我有定义的类型:数据树 = 节点树树 |叶诠释 |无。我想创建一个方法 delete :: Tree -> Int -> Tree,它使用第二个参数中给出的特定 Int 删除所有 Leaf。

【问题讨论】:

  • 即使你是 Haskell 的新手,你至少应该能够实现一些基本情况,例如delete (Leaf n) n' =.
  • 主要问题是,我不知道如何从树中删除叶子。
  • 我唯一知道的是类型:data Tree = Node Tree Tree |叶诠释 |无。但我确信这棵树是一棵叶子上有 Int 值的二叉树。

标签: haskell tree functional-programming int


【解决方案1】:

如果你的树没有任何特定的结构,你可以这样做

delete NIL _ = NIL
delete (Leaf i) int | i == int = NIL
                    | otherwise = Leaf i
delete (Node left right) int = Node (delete left int) (delete right int)

为什么?

delete NIL _ = NIL 因为我们必须处理所有情况,甚至是末端的空树。 _ 代表我们不关心的任何值。

delete (Leaf i) int | i == int = NIL
                    | otherwise = Leaf i

因为我们需要先检查| i== int,看看我们是否要删除节点。如果我们这样做,我们将其替换为空的三个,NIL。否则,我们就不管它。

delete (Node left right) int = Node (delete left int) (delete right int) 因为如果我们在一个节点上,我们需要从leftright 子树中删除int

你最终不会收到一大堆NILs 吗?

是的,我想这可能会发生。你可以清除

prune (Node  NIL      NIL    ) = NIL
prune (Node (Leaf i)  NIL    ) = Leaf i 
prune (Node  NIL     (Leaf i)) = Leaf i 
prune (Node (Leaf i) (Leaf j)) = Node (Leaf i) (Leaf j)
prune (Node  left     right  ) = prune (Node (prune left) (prune right))
prune t = t

前三行去掉左边、右边或两边的NIL,第四行留下两片叶子。

仅当该节点的左子树或右子树之一本身是节点时才调用第五行。为什么prune 三次?也许当你prune leftprune right 其中一个或多个最终是NIL

prune t = t 在一个简洁的模式匹配中同时处理 NILLeaf i

【讨论】:

  • 哇,这是我见过的最好的答案。我要给更多的赞!
  • 要理解的重要一点是,您实际上并没有删除任何内容。相反,您正在创建树的副本,但删除了这些节点。
【解决方案2】:

我建议对 AndrewC 的回答进行一些改进。虽然他的解决方案绝对正确,但它存在一些潜在的性能问题。

问题是:deleteprune 函数都会在每次调用时创建整个树的新副本。无论元素是否实际被删除,都会发生这种情况。

最坏的情况是删除一个不存在的元素。

假设我们有一棵非常大的树,里面有 1M 的整数。由于整数仅存储在叶子中,因此整个树至少包含 2M-1 个节点。 (或者甚至更多,如果树还没有被修剪,因此包含 NIL 节点)。

当我们试图从这么大的树中删除一个不存在的元素时,我们的delete 函数只会复制所有 2M 的节点。 (而prune 将再次复制它们!)

删除现有元素只是稍微好一点。在这种情况下,delete 删除一个叶子,更新它的父节点并复制树的其余部分。 prune 可能会删除更多节点,但会复制其余节点。

为什么会这样?

有两个地方会发生重复。

这一行创建了一个与参数完全相同的新树:

delete (Leaf i) int | ...
                    | otherwise = Leaf i

此外,即使元素不在左右分支中,此行也会创建一棵新树:

delete (Node left right) int = Node (delete left int) (delete right int)

是否可以避免不必要的重复?

是的,当然。如果我们不修改它,我们只需要返回参数。

这是我的版本:

delete t i = fst $ delete' t i
  where delete' NIL _ = (NIL, True)
        delete' t@(Leaf i) int | i == int = (NIL, False)
                               | otherwise = (t, True)
        delete' t@(Node left right) int =
            let (left', unchangedLeft) = delete' left int
                (right', unchangedRight) = delete' right int
            in
              if unchangedLeft && unchangedRight
                then (t, True)
                else (Node left' right', False)

如您所见,我使用了一个辅助函数 delete',它返回一对 (Tree, Bool),如果树没有更改,则第二个元素为 True,否则为 False。

此函数构建一棵与原始树共享大部分节点的新树。它只会改变从根到被删除元素的路径上的节点。

prune 呢?

以上版本不会删除NIL 元素。正如 AndrewC 所指出的,在执行多次删除后,我们可能有一棵树,其中包含很多 NILs。为了解决这个问题,我们可以用类似的方式修改prune,也可以只将其集成到delete中:

delete t i = fst $ delete'' t i
  where delete'' NIL _ = (NIL, True)
        delete'' t@(Leaf i) int | i == int = (NIL, False)
                                | otherwise = (t, True)
        delete'' t@(Node left right) int =
            let (left', unchangedLeft) = delete'' left int
                (right', unchangedRight) = delete'' right int
            in
              if unchangedLeft && unchangedRight
                then (t, True)
                else (newNode left' right', False)

        newNode NIL r = r
        newNode l NIL = l
        newNode l r = Node l r

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多