我已经看到了静态二进制树的基于数组的实现,这些实现不会浪费指针的内存,而是对当前索引执行操作以转到其父或子。是否有任何文章谈论二进制树的类似方法,您必须插入或删除。我可以看到,除非你对允许的插入数有一个上限,否则该数组将不再有用。
答案 0 :(得分:1)
总是可以在数组中构建二叉树,使用简单的算法从父节点中查找子节点。一种常用的方法(特别是对于二进制堆)是使用以下...
left_child_index = (2 * parent_index) + 1
right_child_index = (2 * parent_index) + 2
因此0的根节点有1和2的子节点,1的节点有3和4的子节点等。
这种方案的缺点是,虽然你通过不存储指针来获得空间,但是你通常需要在数组中为未使用的节点留下空隙而占用空间。二进制堆通过完整的二叉树来避免这种情况 - 当前项目数范围内的每个节点都是有效的。这适用于堆,但不适用于二进制搜索树操作。
只要您可以调整数组大小(例如C ++中的std::vector
),就不需要在插入数上加上限,但最终可以使用 lot < / em>数组深层部分的间隙,特别是如果树变得不平衡。
您还需要某种方法来确定数组中的位置是否包含有效节点 - 无法在有效节点中出现的标志或数据值。标志可能存储为打包的位数组,与主节点分开。
另一个缺点是重组树意味着移动数据 - 而不仅仅是调整指针。指针旋转(许多平衡二叉树所需,例如红黑树和AVL树)成为可能非常昂贵的操作 - 它们不仅需要移动通常的三个节点,而且整个子树都来自旋转的节点。 / p>
也就是说,如果你的物品非常小,如果你的树木很小或者你对一棵简单的不平衡树很好,那么这个方案可能会很有用。这可能就像一组整数数据结构一样合理。
BTW - “看似合理”并不意味着“推荐”。即使你设法找到一个效率更高的案例,我也很难相信开发时间是合理的。可能更有用......
多路树在每个节点中包含小的项目数组,而不是通常的一个键。它们最常用于硬盘上的数据库索引。最着名的是B树,B +树和B *树。
多路树具有子节点指针,但对于最多可容纳n个键的节点,子指针的数量通常为n或n + 1 - 不两次n。此外,常见的策略是为分支和叶节点使用不同的节点布局。只有分支节点具有子指针。每个数据项都在叶节点中,并且只有叶节点包含非密钥数据。分支节点纯粹用于指导搜索。由于叶子节点是迄今为止最多的节点,因此在它们中没有子指针是一种有用的节省。
然而 - 多路树节点很少被打包。同样,未使用的阵列插槽有空间开销。通常的规则是每个节点(根除外)必须至少半满。一些实现为避免分裂节点付出了相当大的努力,从而最小化了空间开销,但通常预期的开销大致与项目数成比例。
我还听说过一种树形式,每个节点拥有多个密钥,但每个节点只有两个子指针。我甚至不记得这叫什么,我害怕。
还可以将(父指针,子指针)对存储在单独的数据结构中。这对于使用(父ID,子ID)对的表或(父ID,兄弟索引,子ID)三元组或其他表来表示数据库中的树是相当常见的。一个优点是您不需要存储'null'指针。
然而,可能最好的选择,而不是试图减少或消除存储指针的开销,是为了更好地利用这种开销。线程二叉树更好地利用子指针来支持树的有效遍历 - http://en.wikipedia.org/wiki/Threaded_binary_tree。
答案 1 :(得分:0)
至少在C ++中,使用数组而不是单独分配结构的部分好处是避免了创建每个对象的开销。 (C ++中的结构数组在内存中是连续的,没有标题或分配对齐问题)相比之下,保存一个指针可能很小。
不幸的是,在Java中,一个对象数组不能以这种方式工作,因此使用数组不会给你带来你想象的好处。在C ++中,计算了对每个对象的引用,但在Java中,对每个对象的引用都存储在内存中,即使它们恰好是连续的。
Java为您做的唯一事情就是在64位JVM中使用32位引用。
除非您拥有内存有限的设备或极其庞大的数据结构(例如数百万个元素),否则您不太可能注意到差异,您可以以低于100英镑的价格购买16 GB。