【发布时间】:2015-05-27 08:05:02
【问题描述】:
一般来说,基于二叉树的抽象可以使用实际的链接节点对象来实现,其中每个节点都有指向它的两个子节点的指针,或者一个数组,其中索引 k 中节点的子节点是 2k 和 2k+1。
除了节点的少量额外内存开销外,总体上的复杂性似乎是相同的。
两者相比有什么具体的优势吗?有趣的是,我看到二叉堆倾向于使用数组实现,而二叉搜索树倾向于使用链接节点实现。这有什么原因吗?
【问题讨论】:
标签: binary-tree
一般来说,基于二叉树的抽象可以使用实际的链接节点对象来实现,其中每个节点都有指向它的两个子节点的指针,或者一个数组,其中索引 k 中节点的子节点是 2k 和 2k+1。
除了节点的少量额外内存开销外,总体上的复杂性似乎是相同的。
两者相比有什么具体的优势吗?有趣的是,我看到二叉堆倾向于使用数组实现,而二叉搜索树倾向于使用链接节点实现。这有什么原因吗?
【问题讨论】:
标签: binary-tree
数组不能有效地表示任意形状的二叉树,只有完整树。一棵完全二叉树是所有层都满的,或者除了最深层之外的所有层都是满的,并且最深层的所有节点都尽可能地向左。 (你可以想象,关卡从左到右被节点填充,并且必须在下一个级别开始之前填充一个级别。)
根据定义,堆是完整的二叉树 - 因此使用数组实现是因为它具有出色的内存效率。另一方面,必须支持在任意位置插入和删除的二叉搜索树(因此可能不是完整的树)不能使用数组实现。
【讨论】:
nil
首先,它是一个合理的问题:二叉树确实可以嵌入到数组中。 phari's answer 不正确:可以通过一些努力将任意形状的树嵌入到数组中(只要您有足够的内存)。一个简单的表示将涉及将Node 定义为变体类型:它是Filled 或Empty,其中Filled 包含密钥和辅助数据,Empty 类似于Nil(又名空指针)。如果您需要支持的唯一操作是delete,那么您就已经设置好了:只需执行build 操作以返回一个完整的树,然后执行正常的二叉树delete 操作。无需平衡即可达到O(log n) 复杂性界限(其中n 是树的初始项数)。
还可以通过保持树的平衡形状来实现insert 操作。稍微简化一下,您可以维护一个几乎完整的树,其存储大小不超过2n(其中n 是树中的当前项目数)。当插入一个新项目时,您检查要插入它的适当数组单元格的位置:如果它在分配的存储空间内,您只需将其写入该单元格。否则,您从该单元格的父级开始向上上树,直到找到一个子树,其存储空间足以容纳包括新项目在内的所有项目;如果不存在这样的子树,则将存储空间加倍。找到该子树后,将其重建为几乎完整的形状并将新项目插入正确的数组单元(现在保证在分配的存储中)。所有这一切都可以在摊销的O(log^2(n)) 时间内完成,或者在摊销的O(log(n)) 时间内完成。
以上算法草图基于"Cache Oblivious Search Trees via Binary Trees of Small Height"。
我已经实现了一个名为TeardownTree 的数据结构,它使用了这种嵌入。我支持主分支上的build、delete、delete_range、query_range、iter 操作,以及在insert 分支上的实验性insert 操作。项目页面还包含一些基准,表明数据结构至少在某些用途上绝对可行。
您可能也有兴趣 this blog post 解释如何在恒定辅助空间中构建树(实践中非常快速的方法)。
【讨论】: