在通用哈希表中查找项目?

Posted

技术标签:

【中文标题】在通用哈希表中查找项目?【英文标题】:Finding items in an universal hash table? 【发布时间】:2012-05-12 02:01:08 【问题描述】:

如果项目是随机组织的,表格如何知道从哪里开始查找?

在非随机表中,项目根据某些特征进行组织。 (即名称)。因此,如果该表需要查找有关“John”的任意信息,它可以开始在“J”存储桶中查找。

不过,在通用哈希表中,项目是随机排列的。没有明确的特征。因此,要找到一些关于“John”的任意信息,该表不是必须遍历每个存储桶吗?

这不是非常浪费时间吗?这就像在你家的每个橱柜中寻找一把勺子一样。

【问题讨论】:

关键是理解随机数据结构(或随机算法)的 randomized 部分:) 一个合适的散列函数应该生成确定性的输入(也就是说,对于某些输入 x,它应该总是生成相同的散列 hx 上运行)。这允许哈希表照常运行,其中键实际上只是从输入生成的哈希。 【参考方案1】:

虽然前面的答案基本上是正确的,但它们并没有直接解决通用散列算法的 随机 部分。通用散列算法在计算密钥的散列时不使用随机性。随机数仅在哈希表初始化期间用于从哈希函数族中选择哈希函数。这可以防止可以访问散列函数详细信息的对手设计最坏情况的密钥集。

换句话说,在哈希表的生命周期内,给定键的桶是一致的。但是,不同的实例(例如下次程序运行时)可能会将相同的密钥放在不同的存储桶中。

【讨论】:

谢谢!您做了关键的澄清声明:“随机数仅在哈希表初始化期间用于从哈希函数族中选择哈希函数”。 我阅读了整个 Cormen-Leiserson-Rivest-Stein 算法书籍章节关于这个主题(通用散列),它仍然让我困惑,如果我滚动一个新的散列函数,我该如何搜索一个键每次我插入一些东西。这个答案让一切都到位。谢谢! 这是准确的答案,仅此而已。 我也误读了通用散列的描述。 Cormen/Leiserson 的书指出 在执行开始时我们从精心设计的函数类中随机选择散列函数。”(我的重点)和 “即使对于相同的输入,算法在每次执行时的行为也可能不同”(第 265 页)。我将“执行”解释为执行函数以插入项目。现在很清楚,这意味着执行应用程序或散列类的每个实例化。 我还想澄清一件事。这是否意味着在哈希表的初始化阶段从一系列哈希函数中选择的“那个”哈希函数用于在哈希表的生命周期内对键进行哈希处理,对吗?如果是这样的话,我不明白这个算法有什么这么随机的。在我看来,在选择了一个哈希函数之后,它的“随机性”就相当于除法。【参考方案2】:

哈希看起来或多或少是随机的,但它是确定性的——也就是说,特定的输入总是产生相同的哈希值。

基于此,当您想在哈希表中插入一个项目时,您首先要为该输入生成哈希。然后,您使用它来索引表,并将您的项目插入表中的该位置。在典型情况下,您有一个部分被视为键,并且您有一些与此相关的更多信息(例如,您可能能够按姓名查找人员,并且每个姓名都有关于该人的信息)。

稍后,当您想要查找(相关信息)特定键(在本例中为人)时,您输入并散列该键以在散列表中找到正确的位置来查找该信息。

这确实跳过了一些关键细节,例如您如何处理发生的两个或多个输入以产生 same 哈希值(这是不可避免的,除非您对允许的输入设置一些限制) .有多种方法可以处理此问题,例如仅按顺序查看表格以找到下一个空闲位置,重新散列以在表格中找到另一个位置,或者构建类似哈希到相同值的项目的链接列表。

在任何情况下,可能应该补充一点,在某些用例中,哈希表确实有点像您推测的那样。仅举一个例子,当您想查看所有哈希表的内容(而不是一次只查找一项)时,通常会扫描整个表。即使您的哈希表几乎是空的,您通常也必须从一端扫描到另一端,寻找实际使用的每个条目。当你这样做时,你会以相当随机的顺序获得项目。

这指出了哈希表的另一个缺点——您通常需要与单个原始记录进行精确匹配才能使其正常工作。例如,让我们考虑一些基于我姓氏的查询。假设您对整个姓氏进行了索引,找到“棺材”将是微不足道的——但至少对于大多数普通的哈希函数,搜索“以“Cof”开头的东西会慢得多,就像“找到所有的名字一样”在“棺材”和“戴明”之间。

因此,您说对了一半——虽然哈希表在某些特定情况下通常非常快(主要是搜索完全匹配),但您概述的总体思路(扫描整个表以find the data) 几乎是用于其他目的的唯一选择,因此如果您想要支持除完全匹配之外的任何内容,则可能更可取的是不同的数据结构。

这主要涉及哈希表的最典型用途/类型。可以创建至少在不同程度上弯曲(如果不是彻底破坏)这些规则的哈希函数。在大多数情况下,这些都涉及一些妥协。例如,给定地理信息作为输入,您可以通过简单地截断坐标(或其中一个坐标)来创建一个哈希(排序),以便以较低的精度获得相同的信息。这至少在一定程度上组织了信息,因此靠近的事物最终具有相似的哈希值,从而更容易找到相邻数据。然而,这通常会导致更多的冲突(例如,对于大城市的市中心,您会得到许多哈希值相同的项目)。

特别关注通用散列,这为难题增加了一个额外元素:您拥有一系列散列函数,而不是单个散列函数,您可以从中“随机”选择。当使用通用哈希来实现哈希表时(并非总是如此——它也经常用于消息身份验证代码之类的事情),您通常会在每次插入时随机选择哈希函数物品。相反,您通常会选择一个哈希,并继续使用它,直到遇到一些固定数量的冲突。然后你随机选择另一个哈希函数。

例如,在 Cuckoo 散列(可能是最常用的通用散列)中,您对密钥进行散列以查找位置。如果它已经被占用,你“踢出”那里的现有项目,并重新散列它以找到它的替代位置。它被插入那里。如果该槽已被占用,它会“踢出”该槽中已有的项目,然后重复该模式。

当您搜索某个项目时,您会对其进行哈希处理并查看该位置。如果那是空的,您会立即知道您的项目不存在。如果该插槽已被占用,但不包含您的项目,则您重新散列以找到备用位置。对尽可能多的散列函数继续使用这种模式(在布谷鸟散列的情况下通常只有两个,但您显然可以使用具有更多函数的其他类似算法)。

这可能会失败——进入一个无限循环,或者(几乎等效地)构建一个超过某个预设长度的链。在这种情况下,您重新开始,使用一对不同的哈希函数重新构建表。

当使用开放散列(其中通用散列是其中一种形式)时,删除往往不是微不足道的。特别是,我们必须确保当我们在一个位置移除一个项目时,它不是在该位置发生碰撞的一系列项目的开始。在许多情况下,简单地为插槽添加第三个状态是最有效的:如果它从未被占用,则它只是空的。如果它当前被占用,则它正在使用中。如果一个项目在那里被删除,它就会被删除。这样,当您在搜索一个项目时,如果您遇到“已删除”的插槽,您会继续搜索您的项目(然而,如果您到达一个从未使用过的插槽,您可以立即停止搜索——您的项目清楚从未插入)。

【讨论】:

【参考方案3】:

哈希表不是随机组织的。它是按哈希值组织的。通过哈希值搜索该表以检索正确的哈希组。

【讨论】:

我意识到了,但是哈希值是随机的,不是吗?就像分配给每条信息的值一样?例如,如果为 John 分配了 2 的哈希值,并为 George 分配了 5 的哈希值,如果要求表找到 George,它是否必须搜索 5 存储桶和 3 存储桶?哈希值赋值是随机的,那么表怎么可能知道需要查找的信息的哈希值呢? 计算哈希值。如果要查找 George,则重新计算哈希,得到 5。然后直接进入 5 桶。本质上,您使用哈希值作为哈希表的索引。一个好的散列方法会将输入随机分散到可用的桶中,因此所有桶的大小大致相同。这并不意味着哈希值是随机分配的。有关如何计算哈希的示例,请参阅 isthe.com/chongo/tech/comp/fnv,在本例中为 FNV 哈希。

以上是关于在通用哈希表中查找项目?的主要内容,如果未能解决你的问题,请参考以下文章

在哈希表中查找冲突

Java集合哈希表及哈希函数的实现方式

查找:哈希表

iOS中的哈希表

哈希算法(Hash)

哈希表与哈希(Hash)算法