Java 哈希数组映射Trie(HAMT)

Java 哈希数组映射Trie(HAMT),java,algorithm,data-structures,hash,trie,Java,Algorithm,Data Structures,Hash,Trie,我正试图弄清楚一件事的细节。我得明白。我对尝试很熟悉,我想我已经掌握了HAMT的主要概念 基本上 两种类型的节点: 键/值 Key Value Node: K key V value 索引 为对象生成32位哈希 一次遍历哈希值5位(0-4、5-9、10-14、15-19、20-24、25-29、30-31) 注:最后一步(第7步)仅为2位 在每个步骤中,找到位图中该5位整数的位置。e、 g.integer==5位图==00001 如果该位为1,则该部分哈希存在 如果位为0,则键不存在

我正试图弄清楚一件事的细节。我得明白。我对尝试很熟悉,我想我已经掌握了HAMT的主要概念

基本上

两种类型的节点:

键/值

Key Value Node:
  K key
  V value
索引

  • 为对象生成32位哈希
  • 一次遍历哈希值5位<代码>(0-4、5-9、10-14、15-19、20-24、25-29、30-31) 注:最后一步(第7步)仅为2位
  • 在每个步骤中,找到位图中该5位整数的位置。e、 g.
    integer==5位图==00001
  • 如果该位为1,则该部分哈希存在
  • 如果位为0,则键不存在
  • 如果该键存在,则通过计算位图中0和位置之间的1数来查找该键在表中的索引。e、 g.
    integer==6位图==0101010101索引==3
  • 如果表指向键/值节点,则比较键
  • 如果表指向一个索引节点,则转到2前进一步
  • 我不太了解的部分是碰撞检测和缓解。在链接的文件中,他提到:

    然后将现有密钥插入到新的子哈希表中,然后 添加了新的密钥。每次使用5个以上的散列位时 碰撞概率降低了1/32。偶尔地 可能会消耗整个32位哈希,并且必须计算一个新的哈希 区分这两个键

    如果我要计算一个“新”散列并将对象存储在该新散列中;你如何才能在结构中查找对象?在进行查找时,它不会生成“初始”哈希而不是“重新计算的哈希”

    我一定错过了什么

    顺便说一句:HAMT的性能相当好,在我的测试中它位于哈希映射和树映射之间

    Data Structure                    Add time   Remove time     Sorted add time Sorted remove time   Lookup time     Size     
    Java's Hash Map                   38.67 ms   18 ms           30 ms           15 ms                16.33 ms        8.19 MB        
    HAMT                              68.67 ms   30 ms           57.33 ms        35.33 ms             33 ms           10.18 MB       
    Java's Tree Map                   86.33 ms   78 ms           75 ms           39.33 ms             76 ms           8.79 MB        
    Java's Skip List Map              111.33 ms  106 ms          72 ms           41 ms                72.33 ms        9.19 MB     
    

    HAMT是一种很棒的高性能结构,尤其是当需要不可变对象时,即每次修改后都会创建数据结构的新副本

    至于你关于散列冲突的问题,我发现了一个C#(现在有bug)来说明它是如何工作的:在每次散列冲突中,密钥都会被重新缓存,并递归地重试查找,直到达到最大迭代次数限制

    目前,我还在函数式编程环境中探索HAMP,并学习现有代码。中有几个HAMT的参考实现

    在web上还有一些更简单的实现,它们不处理冲突,但是它们对于理解这个概念很有用。他们在这里

    我找到了一个用图片和链接描述哈姆特的网站,链接到菲尔·巴格韦尔的原创作品

    相关要点:

    在F#中实现HAMT时,我注意到popCount函数的实现非常重要,与链接的下一个答案中描述的简单实现相比,它提供了10-15%。不太好,但是一顿免费午餐

    当键可以是整数并且实现了相关的IntMap结构时,相关的IntMap结构(及其)非常好


    我相信所有这些实现都很好地学习了高效的不可变数据结构和函数式语言,在这些示例中,它们真的非常出色

    这篇文章有两部分我想你可能错过了。第一个是您引用的位之前的位:

    否则,该键将与现有键冲突。在这种情况下,现有密钥 必须替换为子哈希表和现有密钥的下一个5位哈希 计算。如果仍然存在碰撞,则重复此过程,直到没有碰撞为止 发生

    因此,如果表中有对象A,并且添加了发生碰撞的对象B,则它们的键发生碰撞的单元将是指向另一个子表(它们不发生碰撞)的指针

    接下来,您链接的文章的第3.7节描述了在运行前32位末尾时生成新哈希的方法:

    哈希函数被定制为32位哈希。算法要求 散列可以扩展到任意位数。这是由 将该键与表示trie级别的整数组合,零为 根。因此,如果两个键给出了相同的初始散列,则重新散列具有 进一步碰撞的概率为1/2^32


    如果这似乎不能解释任何问题,那么我将更详细地解释这个答案。

    碰撞的可能性可能非常低,通常只对大树有问题。考虑到这一点,最好将冲突存储在叶的数组中,然后线性搜索()

    如果我要计算一个“新”散列并将对象存储在新的 搞砸你怎样才能在数据库中查找对象 结构?在查找时,它不会生成“初始值”吗 散列,而不是“重新计算的散列”

    进行查找时,使用初始哈希。当首字母中的位 散列已用尽,则以下任一条件为真:

  • 我们最终得到一个键/值节点-返回它
  • 我们最终得到一个索引节点——这是我们必须走的提示 通过重新计算一个新的散列来加深理解

  • 这里的关键是散列位耗尽

    感谢您的帮助;目前我仍在努力理解它。我会再打给你。第3.7节假设散列是多个输入的函数,trie级别是某种“种子”,但在Java和C#等语言中,散列代码通常是对象上的原始方法,因此只有一个输入(对象)。我看不出有什么办法可以让第3.7节适用于这种语言,这意味着你能做的最好的事情就是在叶子上存储一个冲突列表,然后线性搜索它。将放入数据结构中的对象包装到
    节点
    类中,然后将
    节点
    而不是包装的对象插入HAMT中。然后你可以定义
    Data Structure                    Add time   Remove time     Sorted add time Sorted remove time   Lookup time     Size     
    Java's Hash Map                   38.67 ms   18 ms           30 ms           15 ms                16.33 ms        8.19 MB        
    HAMT                              68.67 ms   30 ms           57.33 ms        35.33 ms             33 ms           10.18 MB       
    Java's Tree Map                   86.33 ms   78 ms           75 ms           39.33 ms             76 ms           8.79 MB        
    Java's Skip List Map              111.33 ms  106 ms          72 ms           41 ms                72.33 ms        9.19 MB