不需要调整数组大小的缺点 - 对于数组,通常会将哈希值与数组大小取模,并在数组满时调整数组大小并重新插入所有元素,但是使用二叉搜索树,您可以直接将不变的哈希值插入到二叉搜索树中。
如果我们希望任何单个操作永远不会超过一定的时间(如果我们需要调整数组大小,这很可能会发生),那么这可能是相关的,而总体性能则次要,但是可能有更好的方法来解决这个问题。
具有较低的哈希冲突风险,因为您不会将其与数组大小取模,因此可能哈希数的数量会显着增加。这将减少哈希表的最坏情况性能的风险(当大量元素哈希到相同值时的情况)。
实际的最坏情况性能取决于如何解决冲突。这通常通过链接列表进行处理,以获得O(n)的最坏情况性能。但是,我们也可以使用BST获得O(log n)的性能(如Java的哈希表实现中所做的那样,如果某些哈希的元素数量超过阈值)-也就是说,让您的哈希表数组中的每个元素指向一个BST,其中所有元素具有相同的哈希值。
可能使用更少的内存 - 对于数组,您不可避免地会有一些空索引,但对于BST,这些索引根本不需要存在。虽然这不是一个明显的优点,如果它是优点的话。
如果我们假设使用较少见的基于数组的BST实现,则此数组也将具有一些空索引,并且这也需要偶尔调整大小,但这只是一个简单的内存复制,而不需要重新插入所有具有更新哈希值的元素。
如果我们使用典型的基于指针的BST实现,则指针的额外成本似乎会超过在数组中具有一些空索引的成本(除非数组特别稀疏,这通常是哈希表的不好迹象)。
优点:
缺点:
由于哈希表的要求是O(1)查找,如果具有对数查找时间,则不是哈希表。尽管使用二叉搜索树可以在处理冲突时提供一些好处(嗯,不太可能出现问题),但通常情况下,这并不值得权衡——我想不出任何情况,在使用哈希表时不想要保证的O(1)查找时间。
或者,还有一种可能性是通过BST变体来保证对数插入和删除,其中数组中的每个索引都有一个对应的BST节点的引用。这样的结构可能会变得有点复杂,但可以保证O(1)查找和O(logn)插入/删除。
我发现这个东西是在找有没有人做过。我猜可能不是。
今天早上我想到了一个把二叉树实现为由索引存储的行数组的想法。第一行有1,第二行有2,第三行有4(是的,是2的幂)。这种结构的优点是可以使用位移和加减法来遍历树,而不必使用额外的内存来存储双向或单向引用。
这将允许您基于某种可哈希输入快速搜索哈希值,以发现该值是否存在于其他存储中。或者进行哈希冲突(或部分冲突)的搜索。我想不出还有什么其他用途,但对于这些用途来说,它会非常的快。很可能大多数旋转操作都会完全在CPU缓存中执行,并被写成漂亮的线性块放入主存储器中。
它的主要用途将是对随机输入值进行排序。如果数组中的块是两部分,例如哈希和另一个存储的标识符,您可以快速进行比较并快速插入,以发现具有哈希值的项目在另一个位置被保存(例如文件系统节点的UUID,甚至是文件名或其他短标识字符串)。
我会让其他人去想其他的用途,但我正在使用它来作为图论工作搜索表的一部分,以识别Cuckoo Cycle变体的部分碰撞。
我现在正在研究步行公式,以下是:
i = 数组元素的索引
向上走(转到父级):
i>>1-(i+1)%2
(显然,您可能需要测试i是否为零)
向左走(向下和向左):
i<<1+2
(这个和下一个也需要针对结构的2^depth进行测试,以便不会走出边缘并回到根)
向右走(向下和向右):
i<<1+1
正如您所看到的,每次步行都是基于索引的简短公式。左右移位和加法,升序时则需要位移、加法和模数运算。两个指令向下移动,四个指令向上移动(在汇编语言中,或者像上面的C和其他高级语言操作符表示法中)。
编辑: 我可以从进一步的评论中看出,缩短插入时间的好处肯定会有益处。但我认为传统的基于向量的二叉树提供的好处远不及密集版本。密集版本,其中所有节点都在连续的数组中,当它被搜索时,自然地将通过内存以线性方式遍历,这应该有助于减少缓存未命中,从而显着降低搜索的延迟,以及与随机访问相比,通过块顺序流式传输存在延迟问题。
https://github.com/calibrae-project/bast/blob/master/pkg/bast/bast.go
这是我目前正在实现的一个名为“Bifurcation Array Search Tree”的WiP状态。为了快速插入/删除和不会太慢地搜索排序哈希集合,我认为这对于有大量数据通过结构进出或更准确地说,对于更实时的应用程序来说将会非常有益。