你有一些关于更简洁的方法来编写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
and的使用tail
也是非常不习惯的。定义这样一个函数的常用方法是在列表上进行模式匹配。也是unidiomatic 是你检查一个空列表,,x == []
惯用的方法是使用null x
它。在这种情况下,其他守卫要求元素类型是 的实例Ord
,因此没有语义变化,但通常对元素类型x == []
施加Eq
约束,同时null x
适用于任意类型。
最后,尽管您认为您的守卫head x == e
, head x < e
,head x > e
涵盖了所有可能性(对于有效的Ord
实例,它们确实如此 - 除了浮点类型,其中 aNaN
既不等于也不小于也不大于任何值,但这些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
总是产生一棵树,其中每个节点只有一个(或没有,对于最低的)非空子树,即构造的树基本上是一个列表。
定义的最后一个等式中的子句强烈建议树应该是二叉搜索树,但定义不维护该属性。例如
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。之前建议,将单个元素插入树中的情况分开
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
)。