Hash 哈希表:为什么是桶?

Hash 哈希表:为什么是桶?,hash,hashtable,Hash,Hashtable,据我所知,散列函数的要点是尽可能均匀地分布数据,当发生冲突时,您有几种选择: 寻找下一个空插槽 生成一个不同的散列并尝试将其粘贴到其他位置 将其放入溢出容器(可以是列表、另一个哈希表或其他任何内容) 把它放到下一个空闲的桶槽里 最后一个问题让我很困扰,因为如果你要做一个哈希表,每个地址有2个插槽,为什么不做一个两倍大的哈希表呢?也就是说,除非动态分配存储桶。在我的例子中,表的数据位于一个磁盘上,这意味着另一个磁盘访问+管理可变长度的数据。在我看来,水桶仍然是最受欢迎的选择,为什么呢?我遗漏了什么

据我所知,散列函数的要点是尽可能均匀地分布数据,当发生冲突时,您有几种选择:

  • 寻找下一个空插槽
  • 生成一个不同的散列并尝试将其粘贴到其他位置
  • 将其放入溢出容器(可以是列表、另一个哈希表或其他任何内容)
  • 把它放到下一个空闲的桶槽里

  • 最后一个问题让我很困扰,因为如果你要做一个哈希表,每个地址有2个插槽,为什么不做一个两倍大的哈希表呢?也就是说,除非动态分配存储桶。在我的例子中,表的数据位于一个磁盘上,这意味着另一个磁盘访问+管理可变长度的数据。在我看来,水桶仍然是最受欢迎的选择,为什么呢?我遗漏了什么?

    从关于这个问题的评论中的讨论可能可以看出,有许多不同的方法可以实现哈希表。每个人都有自己的权衡

    您的问题是,为什么要使用bucketing系统(闭合寻址,或带链接的哈希)而不是将对象放入下一个空闲插槽(线性探测)。您指出,将存储桶存储在外部内存中需要在内存中的另一个位置进行查找,如果要将内容存储在磁盘上,这不是一个好主意。这些都是合理的担忧。然而,这里有几件事需要记住

    首先,如果您使用的是bucketing系统(每个哈希表槽都是一个bucket,具有相同哈希代码的所有对象都会被扔进同一个bucket),那么与使用开放寻址的线性探测等系统相比,您有一个优势:您需要担心的唯一冲突是具有相同哈希代码的对象。例如,假设在哈希表中插入三个元素,它们的哈希代码分别为1、1和2。在闭合寻址(bucket)中,无论何时执行1的查找,都必须使用哈希代码1检查两个对象,但如果查找对象2,则根本不必执行任何冲突解决。另一方面,如果使用线性探测,则在查找三个元素中的任何一个时都可能发生碰撞。假设对象A有哈希代码1,对象B有哈希代码2,对象C也有哈希代码1。按A、C、B的顺序插入对象将给出此表:

    [ A ] [ C ] [ B ] [   ] [   ]
      1     2     3
    
    现在,执行C或B的查找需要对表进行线性扫描,即使B不会与对象a或C冲突。这可能是一个真正的问题,具体取决于您的应用程序

    另一方面,如果您使用bucketing,正如您所提到的,您需要进行某种外部内存访问,这在主内存(由于引用的局部性)和磁盘上会有点慢。这是一个很好的论点,解释了为什么对于磁盘上的哈希表来说,使用链式哈希不是一个好主意,而线性探测可能是一个合理的折衷方案


    希望这有帮助

    “在我看来,桶仍然是最受欢迎的选择”为什么?第一个选项称为线性哈希,在大多数情况下是最简单且(有点令人惊讶)仍然是最有效的。1.查找下一个空槽与“4.将其放入下一个空闲槽”有何不同?您习惯的术语是每个存储桶有固定数量的“插槽”吗?他们说“每个地址2个插槽”-你是说每个桶?“bucket是动态分配的”-您是否再次引用了“3”-因为动态bucket对我来说听起来像是列表/向量。无论如何,对于磁盘表,加载一个磁盘区域的成本很高,因此最好使用任何空闲bucket,而不是在查找另一个磁盘区域之前。我不建议每个桶有多个“槽”,只要试试另一个桶就行了。一个好主意是使用一个跳转多个存储桶的“置换列表”(可能在加载的磁盘区域内包装几次重试,然后执行蛮力搜索,然后移动到另一个磁盘区域或重新灰化)。置换列表应避免其和重复的运行:例如,1 3 6是ok(3-1!=6-3,n*(3-1)!=6-1),11则是坏的:6-1==11-6,13 ok….@TonyD第一个注释:它不同,因为在4中,每个哈希都有多个预先分配的KVP空间。对,对。不,我的意思是,对我来说唯一有意义的存储桶就是动态分配它们以节省内存,是的。第二条评论:是的,这几乎结束了#2。是的,这听起来是个好主意。@KarliRaudsepp:每个哈希值有多个预先分配的空间的想法没有用。。。考虑到额外的内存使用,最好让散列在所有可用内存上展开,以减少冲突,然后使用讨论过的任何链接/重新灰化/置换列表技术处理冲突。