【问题标题】:How to delete an element from a Leafy Binary Tree (Haskell)如何从多叶二叉树中删除元素(Haskell)
【发布时间】:2019-12-25 00:32:13
【问题描述】:

所以,这棵树不是二叉搜索树。它没有特定的顺序,只是为了快速访问特定索引(第n个元素),而不是一个元素是否存在。

树的形式是这样的:

data Tree a = Leaf a | Node Int (Tree a) (Tree a) deriving Show

对于这棵特定的树,Node 构造函数中的“Int”是该节点下的元素数(或叶子数)。

使用这种结构,我复制了我在网上找到的讲座中可用的部分树函数(我在尝试理解时稍作修改):

buildTree :: [a] -> Tree a
buildTree = growLevel . map Leaf
    where
    growLevel [node]   = node
    growLevel l        = growLevel $ inner l

    inner []           = []
    inner (e1:e2:rest) = e1 <> e2 : inner rest
    inner xs = xs

    join l@(Leaf _)       r@(Leaf _)       = Node 2 l r
    join l@(Node ct _ _)  r@(Leaf _)       = Node (ct+1) l r
    join l@(Leaf _)       r@(Node ct _ _)  = Node (ct+1) l r
    join l@(Node ctl _ _) r@(Node ctr _ _) = Node (ctl+ctr) l r

我还能够创建一些用于在树中移动的基本功能。我做了一个找到第 n 个元素并返回它的方法。我还创建了一个 Path 数据类型并实现了一个函数来将路径(在左侧和右侧)返回到特定索引,以及一个可以通过路径并返回该节点/叶子的函数。

现在,我想做的是删除功能。这里的问题在于树是“多叶的”,或者至少这是给我带来困难的原因。

如果我在删除路径中最终得到一个 Leaf,则没有“Null”或等效项可以替换它。此外,如果我尝试停在最后一条路径(如 [L]),并检查那是否是节点,那么如果它是叶子,则将整个节点替换为对面等,我遇到了改变的问题整个树以反映该更改,而不仅仅是返回删除的结尾,并更改树中的所有数字以反映叶子的更改。

我希望在删除项目时保留订单,就像您要使用列表作为更简单的示例:

del 4 [1, 2, 3, 4, 5, 6, 7] = [1, 2, 3, 4, 6, 7]

如果有更简单的方法来构建树(仍然可以包含重复元素并保持顺序),那是什么?

有没有办法使用这种方法删除元素?

【问题讨论】:

  • 旁注:将join/(&lt;&gt;)写成l &lt;&gt; r = Node (count l + count r) l r,提取count :: Tree a -&gt; Int作为自己的函数不是更好吗?我想你也想给Node添加一个爆炸:Node !Int (Tree a) (Tree a)
  • @HTNW 如果我实现了一个计数功能,那么每次更改某些内容时对整个节点的计数不会违背树速度的目的吗?另外,我不太了解 bang 模式,但据我了解,这似乎是个好主意。
  • count (Leaf _) = 1; count (Node n _ _) = ncount 使树的要点更加清晰:Tree 是正常的二叉树,除了count 函数比正常更有效。
  • empty = Node 0 empty empty.

标签: haskell recursion binary-tree


【解决方案1】:

如果我......用对面替换整个节点......我遇到了更改整个树以反映该更改的问题,而不仅仅是返回删除的结尾,并更改所有数字树来反映树叶的变化。

嗯,不是整个树 - 只是从已删除节点返回到根的路径。这不正是你想要的吗?

我想第一步是,定义“删除”的含义。删除后未删除节点的索引应该保持不变,还是删除节点之后的节点的索引应该减一?也就是说,给定:

tree :: [a] -> Tree a
-- get and del both 0-indexed, as in your example
get :: Int -> Tree a -> Maybe a
del :: Int -> Tree a -> Tree a

那么当然

get 5 $ tree [1..7]

应该产生Just 6。但是呢

get 5 . del 4 $ tree [1..7]

?如果你希望它仍然产生Just 6(你的树中有一个“空白”点,以前是 5),我认为这是一个相当棘手的概念。如果您定义 Leaf (Maybe a) 而不是 Leaf a,则可以放入 Nothing 以腾出空间,但这只能解决问题:插入仍然会移动索引。

我认为生成Just 7 更简单,使del 4 $ tree [1..7]tree [1,2,3,4,6,7] 相同。如果这是您的目标,那么您只需重新编号从已删除节点回到根的路径上的所有节点:无法回避这样一个事实,即它们现在都少了一个叶子后代。但是树中的其他节点可以保持不变。

作为参考,del 的一种可能实现:

count :: Tree a -> Int
count (Leaf _) = 1
count (Node s _ _) = s

del :: Int -> Tree a -> Maybe (Tree a)
del n t | n < 0 || n >= size || size <= 1 = Nothing
        | otherwise = go n t
  where size = count t
        go n (Leaf _) = Nothing
        go n (Node s l r) | n < size = reparent flip l r
                          | otherwise = reparent id r l
          where reparent k c o = pure . maybe o (k (Node (s - 1)) o) $ go n c
                size = count l

【讨论】:

    【解决方案2】:

    如果我在删除路径中最终得到一个 Leaf,则没有“Null”或等效项可以替换它。

    好吧,做一个:)。这就是Maybe 的用途:当你从Tree 中删除一个元素时,你不能期望得到一个Tree,因为Tree 被定义为非空。您需要通过包装Maybe 来明确添加空虚的可能性。删除也可能因越界错误而失败,我用Either Int 表示并合并到逻辑中。

    delete :: Int -> Tree a -> Either Int (Maybe (Tree a))
    delete i t | i >= max = Left (i - max) where max = count t
    delete _ (Leaf _) = Right Nothing
    delete i (Node n l r) = case delete i l of
      Left i' -> Just <$> maybe l (Node (n - 1) l) <$> delete i' r
      Right l' -> Right $ Just $ maybe r (\x -> Node (n - 1) x r) l'
    

    count 是我在 cmets 中推荐的:

    count :: Tree a -> Int
    count (Leaf _) = 1
    count (Node n _ _) = n
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-04
      • 1970-01-01
      • 2012-03-26
      • 1970-01-01
      • 2016-03-18
      相关资源
      最近更新 更多