你能用 O(log n) 插入在 Haskell 中实现二叉搜索树吗?

Can you implement Binary Search Tree in Haskell with O(log n) insertion?

如果我没理解错的话,在Haskell[=20=中修改(插入或删除)一个二进制搜索树 ] 需要复制整棵树,所以实际上是 O(n)。有没有办法在 O(log n) 中实现它,或者编译器可能会优化 O(n) 插入到 O(log n)“幕后”?

If I understand correctly, modifying (insertion or deletion) a Binary Search Tree in Haskell requires copying the whole tree, so practically making it being O(n).

您不需要复制整棵树。实际上,让我们使用一个简单的 不平衡 二叉搜索树,例如:

data Tree a = Node (Tree a) a (Tree a) | Empty deriving (Eq, Show)

然后我们可以插入一个值:

insertIn :: Ord a => a -> Tree a -> Tree a
insertIn x = go
  where go Empty = Node Empty x Empty
        go n@(Node l v r)
          | x < v = Node (go l) v <b>r</b>
          | x > v = Node <b>l</b> v (go r)
          | otherwise = n

这里我们重用r以防我们构造一个Node (go l) v r,我们重用l 以防我们构造一个 Node l v (go r)。对于我们访问的每个节点,我们创建一个新节点,其中两个子树之一用于新节点。这意味着新树将指向与原始树相同的 个子树对象。

在这个例子中,新节点的数量因此与 O(d)d 树的深度成比例。如果树相当平衡,那么它将插入 O(log n).

当然你可以改进算法,定义一棵AVL树或红黑树,在节点中存储更多关于平衡的信息,这样你就可以保证O(log n)插入时间。

这里所有数据都是不可变的,这有助于重用部分树:我们知道 lr 不能改变,所以这两棵树将共享大量节点如果您想同时使用原始树和新树,则可以减少所需的内存量。

如果没有必要引用旧树,垃圾收集器最终将收集已被新树替换的“旧”节点。