我建议对 AndrewC 的回答进行一些改进。虽然他的解决方案绝对正确,但它存在一些潜在的性能问题。
问题是:delete 和 prune 函数都会在每次调用时创建整个树的新副本。无论元素是否实际被删除,都会发生这种情况。
最坏的情况是删除一个不存在的元素。
假设我们有一棵非常大的树,里面有 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