【问题标题】:Hash table: why buckets?哈希表:为什么是桶?
【发布时间】:2014-06-27 05:02:41
【问题描述】:

据我所知,散列函数的目的是尽可能均匀地分配数据,当您遇到冲突时,您有多种选择:

  1. 寻找下一个空槽
  2. 生成不同的哈希并尝试将其粘贴到其他地方
  3. 将其放入溢出容器(可以是列表、另一个哈希表或其他)
  4. 将其放入下一个空闲桶槽中

最后一个困扰着我,因为如果你要为每个地址制作一个带有 2 个插槽的哈希表,为什么不制作一个两倍大的哈希表呢?那是除非桶是动态分配的。在我的情况下,表的数据位于磁盘上,这意味着另一个磁盘访问+管理可变长度数据。在我看来,虽然水桶仍然是最受青睐的选择,但这是为什么呢?我错过了什么?

【问题讨论】:

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

标签: hash hashtable


【解决方案1】:

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

您的问题是为什么要使用分桶系统(封闭寻址或链式散列)而不是将对象放入下一个空闲槽(线性探测)。您指出将存储桶存储在外部内存中需要在内存中的另一个位置进行查找,如果您将内容存储在磁盘上,这不是一个好主意。这些都是合理的担忧。但是,请记住以下几点。

首先,如果您使用的是分桶系统(每个哈希表槽都是一个桶,并且所有具有相同哈希码的对象都被扔到同一个桶中),那么您比使用 open 的线性探测等系统具有一个优势寻址:您需要担心的唯一冲突是具有相同哈希码的对象。例如,假设您将三个元素插入到哈希表中,并且它们的哈希码是 1、1 和 2。在封闭寻址(存储桶)中,每当您执行查找 1 时,您都必须使用哈希码 1,但如果您查找对象 2,则根本不需要进行任何冲突解决。另一方面,如果您使用线性探测,则在查找三个元素中的任何一个时可能会发生冲突。假设对象 A 的哈希码为 1,对象 B 的哈希码为 2,对象 C 的哈希码也为 1。按 A、C、B 的顺序插入对象会得到这张表:

[ A ] [ C ] [ B ] [   ] [   ]
  1     2     3

现在,对 C 或 B 执行查找将要求您对表进行线性扫描,即使 B 没有与对象 A 或 C 发生冲突。根据您的应用程序,这可能是一个真正的问题。

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

希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-20
    • 1970-01-01
    • 1970-01-01
    • 2011-08-22
    • 1970-01-01
    • 2012-02-22
    • 2012-11-15
    相关资源
    最近更新 更多