最后一个元素没有在Tree中插入

时间:2012-09-22 02:38:51

标签: haskell

因此我被要求在Haskell中创建一个二进制树,将整数列表作为输入。以下是我的代码。我的问题是列表的最后一个元素没有插入树中。例如[1,2,3,4]它只插入到树中,直到“3”和4未插入树中。

    data ArbolBinario a = Node a (ArbolBinario a) (ArbolBinario a) | EmptyNode
deriving(Show)

    insert(x) EmptyNode= insert(tail x) (Node (head x) EmptyNode EmptyNode)

    insert(x) (Node e izq der)
     |x == [] = EmptyNode --I added this line to fix the Prelude.Head Empty List error, after I added this line the last element started to be ignored and not inserted in the tree
     |head x == e = (Node e izq der)
     |head x < e = (Node e (insert x izq) der)
     |head x > e = (Node e izq (insert x der))

关于最新情况的任何想法?非常感谢帮助

3 个答案:

答案 0 :(得分:4)

这里没有帮助的是你在混淆问题。为什么没有一个将单个元素插入树中的函数,而不是一个将列表中的所有元素添加到树中的函数,而不是将一个列表插入到树中的单个方法?例如

data ArbolBinario a = Node a (ArbolBinario a) (ArbolBinario a)
                    | EmptyNode
                    deriving(Show)

-- insert handles only one element
insert :: (Ord a) => a -> ArbolBinario a -> ArbolBinario a
insert x EmptyNode = Node x EmptyNode EmptyNode
insert x n@(Node e izq der)
  | x == e = n
  | x < e = (Node e (insert x izq) der)
  | x > e = (Node e izq (insert x der))

-- insertList folds the list into a tree
insertList :: (Ord a) => [a] -> ArbolBinario a -> ArbolBinario a
insertList xs t = foldl (\a x -> insert x a) t xs

使用类似foldl的内容,您无需担心到达列表末尾时会发生什么。结果是:

insertList [5, 3, 7, 9, 1, 4, 2, 6] EmptyNode
-- output:
-- Node 5 (Node 3 (Node 1 EmptyNode (Node 2 EmptyNode EmptyNode)) (Node 4 EmptyNode EmptyNode)) (Node 7 (Node 6 EmptyNode EmptyNode) (Node 9 EmptyNode EmptyNode))

希望有所帮助。

修改----

我还想指出,关注其他Haskell函数的作用并改变参数的顺序可能是个更好的主意。见:

data ArbolBinario a = Node a (ArbolBinario a) (ArbolBinario a)
                    | EmptyNode
                    deriving(Show)

insert :: (Ord a) => ArbolBinario a -> a -> ArbolBinario a
insert EmptyNode x = Node x EmptyNode EmptyNode
insert n@(Node e izq der) x
  | x == e = n
  | x < e = (Node e (insert izq x) der)
  | x > e = (Node e izq (insert der x))

insertList :: (Ord a) => ArbolBinario a -> [a] -> ArbolBinario a
insertList = foldl insert

这意味着您的功能可以更好地与通常的嫌疑人(例如折叠,扫描等)一起使用。您可以看到insertList的定义如何变得更简单。

仅供参考:)

答案 1 :(得分:3)

您有一些更清晰的方式来编写insert函数的建议,但到目前为止还没有人告诉您实现有什么问题,所以让我从这开始:

data ArbolBinario a = Node a (ArbolBinario a) (ArbolBinario a) | EmptyNode
                      deriving(Show)

insert(x) EmptyNode= insert(tail x) (Node (head x) EmptyNode EmptyNode)

insert(x) (Node e izq der)
 |x == [] = EmptyNode
 |head x == e = (Node e izq der)
 |head x < e = (Node e (insert x izq) der)
 |head x > e = (Node e izq (insert x der))

为简洁起见,我使用较短的列表并将EmptyNode缩写为E

insert [1,2] E
~> insert [2] (Node 1 E E)
--   2 > 1, so the last clause, head x > e, is used
~> Node 1 E (insert [2] E)
-- insertion in empty tree, first equation is used
~> Node 1 E (insert [] (Node 2 E E))
-- Now the first clause of the second equation is used
~> Node 1 E (E)

当插入所有元素并到达列表末尾时,您将删除该节点,而不是不执行任何操作。解决此问题的最小变化是将insert的第二个等式的第一个子句更改为

 |x == [] = Node e izq der

然而,这仍然会留下一个失败案例(你已经拥有),

insert [] EmptyNode = insert (tail []) (Node (head []) EmptyNode EmptyNode)

会导致*** Exception: Prelude.tail: empty list

除了造成上述错误的原因外,headtail在这里的使用也非常单一。定义这样一个函数的通常方法是在列表上进行模式匹配。另外,您可以检查空列表x == [],这是惯用的方法,即使用null x。在这种情况下,其他防护需要元素类型为Ord的实例,因此没有语义更改,但通常,x == []对元素类型强加Eq约束,null x适用于任意类型。

最后,虽然你认为你的警卫head x == ehead x < ehead x > e涵盖所有可能性(对于有效的Ord个实例,但他们确实如此 - 除了浮点类型,其中a NaN既不等于也不小于任何值,但是这些Ord实例是否有效是一个有争议的问题),编译器无法确定这一点,并且会(当要求警告这些通常应该是这样的事情,始终用-Wall编译来警告insert定义中的非详尽模式。为了覆盖所有情况,编译器知道所有情况都被覆盖,最后一个警卫应该有otherwise条件。

将您的代码置于更惯用的形状(并修复未解决的insert [] EmptyNode错误)会导致

insert :: Ord a => [a] -> ArbolBinario a -> ArbolBinario a
insert []      t         = t     -- if there's nothing to insert, don't change anything
insert (x:xs)  EmptyNode = insert xs (Node x EmptyNode EmptyNode)
-- Using as-patterns, `l` is the entire list, `x` its head, `t` the entire tree
insert l@(x:_) t@(Node e izq der)
    | x == e             = t
    | x < e              = Node e (insert l izq) der
    | otherwise          = Node e izq (insert l der)

现在我们可以寻找更多问题了。 insert的一个可能意外的方面是,如果要插入的元素列表的头部已经在树中,则整个列表将被丢弃并且树完全不变,例如。

insert [1 .. 10] (Node 1 EmptyNode EmptyNode) = Node 1 EmptyNode EmptyNode

处理此类事情的常用方法是仅删除列表的头部并仍插入其余元素。这可以通过将定义的最后一个等式改为

来实现
insert l@(x:xs) t@(Node e izq der)
    | x == e             = insert xs t
    | x < e              = Node e (insert l izq) der
    | otherwise          = Node e izq (insert l der)

更可能是非预期的方面

  • insert xs EmptyNode总是生成一棵树,其中每个节点只有一个(或没有,对于最低的)非空子树,即构造的树基本上是一个列表。
  • 定义的最后一个等式中的子句强烈建议树应该是二叉搜索树,但该属性不是由定义维护的。 e.g。

    insert [1,10] (Node 3 (Node 2 E E) (Node 7 E E))
    ~> Node 3 (insert [1,10] (Node 2 E E)) (Node 7 E E)
    ~> Node 3 (Node 2 (insert [1,10] E) E) (Node 7 E E)
    ~> Node 3 (Node 2 (insert [10] (Node 1 E E)) E) (Node 7 E E)
    ~> Node 3 (Node 2 (Node 1 E (Node 10 E E)) E) (Node 7 E E)
    
                           3
                          / \
                         /   \
                        2     7
                       /
                      /
                     1
                      \
                       \
                        10
    

解决这些问题的最佳方法是,OJ. suggested before,将单个元素插入树中的情况分开

insertOne :: Ord a => a -> ArbolBinario a -> ArbolBinario a
insertOne x EmptyNode = Node x EmptyNode EmptyNode
insertOne x t@(Node e izq der)
    | x == e    = t
    | x < e     = Node e (insertOne x izq) der
    | otherwise = Node e izq (insertOne x der)

并使用它从列表中插入每个元素,从顶部找到它的位置:

insertList :: Ord a => [a] -> ArbolBinario a -> ArbolBinario a
insertList []     t = t
insertList (x:xs) t = insertList xs (insertOne x t)
-- Alternative way:
-- insertList (x:xs) t = insertOne x (insertList xs t)

这些计算模式非常普遍,以至于它们已在Prelude中定义的函数中捕获:

insertList xs t = foldl (flip insertOne) t xs
-- or, for the alternative way:
-- insertList xs t = foldr insertOne t xs

正如您所看到的,使用insertOne的自然参数顺序,对于左侧折叠,我们需要应用flip组合子来交换其参数顺序,这暗示了自然的事实列表的折叠操作是正确的折叠,foldr

然而,由于insertOne在它可以做任何事情之前需要知道它的树参数,所以它不是一个量身定制的功能,可以在右侧折叠中使用,在左侧折叠中使用它可以更有效(但是实际上是效率提升,必须使用严格的左侧折叠foldl',可以从Data.List获得,以及更严格的insertOne版本。

答案 2 :(得分:2)

为了解决这个问题,我使用了另一个功能

data Tree a = Node a (Tree a) (Tree a) | EmptyNode deriving(Show)

insert :: Ord a => a -> Tree a -> Tree a
insert x EmptyNode = Node x EmptyNode EmptyNode
insert x (Node y left right)
    | x == y = (Node y left right)
    | x <  y = (Node y (insert x left) right)
    | x >  y = (Node y left (insert x right))

buildtree :: Ord a => [a] -> Tree a
buildtree []     = EmptyNode
buildtree (x:xs) = insert x (buildtree xs)

tree :: Ord a => [a] -> Tree a
tree xs = buildtree (reverse xs)

要构建二叉树,需要调用buildtree函数。问题是你需要先反转列表。因此,tree将完成这项工作。

*Main> insert 5 (insert 12 (insert 2 (insert 10 EmptyNode)))
Node 10 (Node 2 EmptyNode (Node 5 EmptyNode EmptyNode)) (Node 12 EmptyNode EmptyNode)

*Main> tree [10,2,12,5]
Node 10 (Node 2 EmptyNode (Node 5 EmptyNode EmptyNode)) (Node 12 EmptyNode EmptyNode)

*Main> buildtree [5,12,2,10]
Node 10 (Node 2 EmptyNode (Node 5 EmptyNode EmptyNode)) (Node 12 EmptyNode EmptyNode)

<强>更新

你可以避免使用其他功能,这样:

insert2 :: Ord a => [a] -> Tree a -> Tree a
insert2 []     t = t
insert2 (x:xs) EmptyNode = insert2 xs (Node x EmptyNode EmptyNode)
insert2 (x:xs) (Node y left right)
            | x == y = insert2 xs (Node y left right)
            | x <  y = insert2 xs (Node y (insert2 [x] left) right)
            | x >  y = insert2 xs (Node y left (insert2 [x] right))

然而,它不如使用foldl或其他方式那样有效,唯一的好处就是只使用一个函数来完成所有操作,不需要反向。

*Main> insert2 [10,2,12,5] EmptyNode
Node 10 (Node 2 EmptyNode (Node 5 EmptyNode EmptyNode)) (Node 12 EmptyNode EmptyNode)