因此我被要求在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))
关于最新情况的任何想法?非常感谢帮助
答案 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
。
除了造成上述错误的原因外,head
和tail
在这里的使用也非常单一。定义这样一个函数的通常方法是在列表上进行模式匹配。另外,您可以检查空列表x == []
,这是惯用的方法,即使用null x
。在这种情况下,其他防护需要元素类型为Ord
的实例,因此没有语义更改,但通常,x == []
对元素类型强加Eq
约束,null x
适用于任意类型。
最后,虽然你认为你的警卫head x == e
,head x < e
,head 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)