二叉树,数组与链接

Binary Trees, arrays vs linked

一般来说,基于二叉树的抽象可以使用实际的链接节点 objects 来实现,其中每个节点都有指向它的两个 children 的指针,或者一个数组,其中 children 索引 k 中的节点是 2k 和 2k+1.

除了节点的小额外内存开销外,一般的复杂度似乎是相同的。

两者之间有什么具体的优势吗?有趣的是,我发现二叉堆倾向于使用数组实现,而二叉搜索树倾向于使用链接节点实现。有什么原因吗?

数组不能有效表示任意形状的二叉树,只能表示完整棵树。完全二叉树是所有层都满的,或者除最深层以外的所有层都满的,最深层的所有节点都尽可能向左。 (你可以想象,关卡从左到右都是节点,必须先填满一层才能开始下一层。)

根据定义,堆是完整的二叉树 - 因此由于其卓越的内存效率而使用数组实现。另一方面,必须支持在任意位置插入和删除的二叉搜索树(因此可能不是完整的树)不能使用数组实现。

首先,一个合理的问题:二叉树确实可以嵌入到数组中。 是不正确的:可以通过一些努力将任意形状的树嵌入到数组中(只要你有足够的内存)。一种直接的表示形式是将 Node 定义为变体类型:它是 FilledEmpty,其中 Filled 包含密钥和辅助数据,而 Empty 类似于 Nil (又名空指针)。如果你需要支持的唯一操作是delete,那么你就全部设置好了:只需将build操作实现到return一棵完整的树,然后再实现普通的二叉树delete 操作。无需平衡即可达到 O(log n) 复杂性界限(其中 n 是树的初始项目数)。

也可以通过保持树的平衡形状来实施 insert 操作。稍微简化一下,您维护一个几乎完整的树,其存储大小不超过 2n(其中 n 是树中的当前项目数)。插入新项时,检查要插入的适当数组单元格的位置:如果它在分配的存储空间内,则只需将其写入该单元格。否则,您从该单元格的父级开始向上爬树,直到找到一个子树,该子树的存储空间足以容纳所有项目,包括新项目;如果不存在这样的子树,则将存储空间加倍。找到该子树后,将其重建为接近完整的形状,并将新项插入正确的数组单元格(现在保证在分配的存储空间内)。所有这些都可以在摊销的 O(log^2(n)) 时间内完成,或者在摊销的 O(log(n)) 时间内完成更多工作。

以上算法草图基于"Cache Oblivious Search Trees via 小高度二叉树.

我实现了一个名为TeardownTree的数据结构,它使用了这种嵌入。我支持 master 分支上的 builddeletedelete_rangequery_rangeiter 操作,以及 [=] 上的实验性 insert 操作21=] 分支。项目页面还包含一些基准,表明数据结构至少对于某些用途绝对可行。

您可能还对 this blog post 解释如何在常量辅助 space 中构建树感兴趣(一种在实践中非常快速的方法)。