(何时)哈希表查找为O(1)?


70

人们通常说哈希表查找是在恒定时间内进行的:您计算哈希值,这将为数组查找提供索引。但这忽略了碰撞。在最坏的情况下,每一项都恰好落在同一存储桶中,并且查找时间变为线性()。Θ(n)

数据上是否存在可以使哈希表查找真正变为?这是仅是平均水平,还是哈希表可以进行最坏情况查找?O 1 O(1)O(1)

注意:我是从程序员的角度出发的;当我将数据存储在哈希表中时,它几乎总是字符串或某些复合数据结构,并且数据在哈希表的生存期内发生变化。因此,尽管我欣赏有关完美哈希的答案,但从我的观点来看,它们很可爱,但很有趣,而且不切实际。

PS跟进:哈希表操作O(1)适用于哪种数据?


3
您可以使用摊销访问时间吗?通常,哈希表的性能将在很大程度上取决于您准备允许多少稀疏哈希表的开销以及实际哈希值的分布方式。O(1)
拉斐尔

5
哦,顺便说一句:您可以通过使用(平衡)搜索树而不是列表来避免线性最坏情况的行为。
拉斐尔

1
@Raphael我会很感兴趣一个答案(沿粗线解释)何时可以指望摊销的以及何时不能指望。至于哈希值的分布方式,这实际上是我的问题的一部分:我怎么知道?我知道散列函数应该很好地分配值;但是如果他们总是这样做,最坏的情况就永远不会发生,这没有道理。O(1)
吉尔斯2012年

1
也要注意过早的优化;对于较小的数据(几千个元素),我经常会看到平衡的二叉树表现出比哈希表更好的性能,原因是开销较低(字符串比较比字符串哈希便宜很多)。O(logn)
isturdy

Answers:


41

有两种设置可让您获得最坏的情况。O(1)

  1. 如果您的设置是静态的,则FKS哈希将为您提供最差情况的保证。但正如您指出的那样,您的设置不是静态的。O(1)

  2. 如果使用杜鹃哈希,则查询和删除是最坏的情况,但插入仅是。如果您对插入的总数有上限,并且将表大小设置为大约大25%,则杜鹃哈希效果很好。O 1 O(1)O(1)

还有更多的信息在这里


3
您可以扩展FKS和杜鹃吗?这两个词对我来说都是新的。
吉尔斯2012年

1
动态完美哈希怎么样?它具有最坏情况的查找和摊销的插入和删除。(citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.30.8165O 1 O(1)O(1)

2
FKS是(Fredman,Komlós,Szemerédi)的缩写,杜鹃是一种鸟类的名字。它用于这种类型的哈希,因为布谷鸟雏鸟将同胞卵从巢中推出。这在某种程度上类似于这种具有方法的功能。
乌里

1
@Suresh:真的吗?我以为您需要独立函数,而这些函数总是与需要扩展器相关联。我站得住了。将会删除我的评论。logn
路易

1
正如@Suresh指出的那样,要对该答案做出更有用的评论,杜鹃哈希将在没有理论上用于分析它的奇特(大)哈希函数的情况下很好地工作。
路易(Louis)

21

此答案总结了TAoCP第3卷第6.4节的部分内容。

假设我们有一组值,其中要存储在大小为的数组中。我们使用哈希函数 ; 通常,。我们称的负载因子的。在这里,我们假设自然;在实际情况下,我们有,并且必须映射到自己。Ñ ħ V [ 0 .. 中号中号« | V | α = nVnAmh:V[0..M)M|V|=中号«中号α=nmAm=MmMm

第一个观察结果是,即使具有统一的特征¹,两个具有相同哈希值的值的可能性也很高;这本质上是臭名昭著的生日悖论的一个例子。因此,我们通常将不得不处理冲突,并且可以放弃最坏情况访问时间的希望。O1 hO(1)

但是,一般情况呢?让我们假设中的每个键都以相同的概率出现。平均检查条目数(成功搜索)。(搜索失败)取决于所使用的冲突解决方法。C S n C U n[0..M)CnSCnU

链式

每个数组条目都包含(指向其头部的)链接列表。这是个好主意,因为预期的列表长度很小()即使发生碰撞的可能性很高。最终,我们得到 Ç小号ñ1+αnm 通过将列表(部分或全部)存储在表中,可以稍作改进。

CnS1+α2 and CnU1+α22.

线性探测

插入(重新搜索值),请 依次检查位置 h v h v 1 ... 0 m 1 h v + 1直到空位置(resp 。v)中找到。优点是我们在本地工作,没有二级数据结构;但是,平均访问次数因α 1而不同v

h(v),h(v)1,,0,m1,,h(v)+1
vα1α<0.75
CnS12(1+11α) and CnU12(1+(11α)2).
但是, 对于,性能可与chaining²相媲美。α<0.75

双重散列

与线性探测类似,但搜索步长由与互质的第二个哈希函数控制。没有给出正式的推导,但是经验观察表明 此方法已由Brent修改;他的变体会通过更便宜的搜索来增加插入费用。Ç 小号Ñ1M

CnS1αln(11α) and CnU11α.

请注意,从各个表中删除元素和扩展表的难度对于相应方法而言具有不同的难度。

最重要的是,您必须选择一种适合您的典型用例的实现。如果不总是保证,可以在预期的访问时间。根据所使用的方法,保持低很重要。您必须权衡(预期)访问时间与空间开销。很显然,一个好选择也很重要。α ħO(1)αh


1]由于任意愚蠢的无知程序员可能会提供,因此关于其质量的任何假设实际上都是一个难题。 2]请注意,这与使用Java的建议如何一致。h
Hashtable


10

完美散列函数可以被定义为从一组的单射函数到整数的子集。如果存在完美的哈希函数来满足您的数据和存储需求,则可以轻松实现行为。例如,您可以从哈希表获得性能,以执行以下任务:给定整数数组和整数集,请确定是否对每个包含。预处理步骤将包括在创建一个哈希表,然后将中的每个元素与之对照S{0,1,2,...,n}O(1)O(1)lSlxxSO(|l|)SO(|S|)。总共是。使用线性搜索的简单实现可能是;使用二进制搜索,您可以执行(请注意,此解决方案是空间,因为哈希表必须将不同整数映射到不同的bin)。O(|l|+|S|)O(|l||S|)O(log(|l|)|S|)O(|l|)l

编辑:要澄清如何在生成哈希表:O(|l|)

列表含有从一组有限的整数,可能具有重复序列,和。我们要确定是否在。为此,我们为元素预先计算了一个哈希表:一个查找表。哈希表将对函数进行编码。为了定义,最初假设lUNSUxSllh:U{true,false}hh(x)=falsexUylh(y)=trueO(|l|)O(|U|)

注意,我的原始分析假设至少包含不同的元素。如果包含较少的不同元素(例如),则空间需求可能会更高(尽管不超过)。lO(|U|)O(|1|)O(|U|)

编辑2:哈希表可以存储为一个简单的数组。哈希函数可以是上的恒等函数。注意,身份函数是一个完美的哈希函数。Uh


O(|l|)O(|S|)O(|l||S|)

hh:U{false,true}h

@Gilles基本上只是用作列表成员的查找表。当您具有一个已知且廉价的逆函数的完美哈希函数时,无需存储事物本身,只需存储1位(是否已添加具有唯一哈希的事物)。如果可能发生冲突,我认为将其称为布隆过滤器,但无论如何可以对成员资格问题提供明确的“否”,这在许多情况下仍然有用。
Patrick87 2012年

9

O(1)

O(1)O(1)O(1)O(1)


完美的哈希函数将是完美的,但是我如何获得一个呢?我要花多少钱?我怎么知道最大或预期的碰撞次数是多少?
Gilles 2012年

2
@Gilles完美的哈希函数是将为所有可能的输入生成唯一哈希的任何函数。如果您可能的输入是有限的(并且是唯一的),这很容易做到。
拉菲·凯特勒

1
@RafeKettler我的输入通常是字符串或复合数据结构,并且我通常会在数据演变时添加和删除条目。我如何为此做一个完美的哈希?
Gilles 2012年

4
是的,但这就是重点。如果域大于范围,则确定性完美哈希函数不存在。
Suresh 2012年

@Suresh:如果允许您选择一个新的哈希函数并在发生冲突时增加表的大小,则始终可以找到一个(确定性)哈希函数,该函数针对表中已有的数据以及一个新的您要插入的商品-没有碰撞(“完美”)。这就是为什么动态完美哈希会定期选择一个随机的新哈希函数的原因。
大卫·卡里
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.