【问题标题】:Hash table runtime complexity (insert, search and delete)哈希表运行时复杂度(插入、搜索和删除)
【发布时间】:2012-03-02 03:06:48
【问题描述】:

为什么我总是在哈希表上看到这些函数的不同运行时复杂性?

在 wiki 上,搜索和删除是 O(n)(我认为哈希表的重点是不断查找,所以如果搜索是 O(n),那还有什么意义)。

在不久前的一些课程笔记中,我看到了取决于某些细节的各种复杂性,包括所有 O(1) 的复杂性。如果我可以得到所有 O(1),为什么还要使用任何其他实现?

如果我在 C++ 或 Java 等语言中使用标准哈希表,我可以预期时间复杂度是多少?

【问题讨论】:

  • 一个完美的方法是 O(1) 查找,但为此您必须在设计表格时知道数据是什么。
  • O(n) 是最坏情况,O(1) 是平均情况。在最坏的情况下,您可能会将 N 个元素都插入到同一个桶中。那么,对于这个数据集,删除和搜索也会是O(n)。

标签: algorithm data-structures hash time-complexity hashtable


【解决方案1】:

一些哈希表 (cuckoo hashing) 保证 O(1) 查找

【讨论】:

  • * 概率很高。使用动态完美哈希二级数据结构进行链接实际上可以实现 O(1)。
【解决方案2】:

Hash tablesO(1) 平均和amortized 情况复杂度,但是它受到O(n) 最坏情况时间复杂度的影响。 [我认为这就是你的困惑所在]

哈希表的时间复杂度最差 O(n) 有两个原因:

  1. 如果太多元素被散列到同一个键中:查看该键可能需要O(n) 时间。
  2. 一旦哈希表通过了它的load balance - 它必须重新哈希[创建一个新的更大的表,并将每个元素重新插入到表中]。

但是,据说是O(1) 平均摊销案例,因为:

  1. 很少有很多项目会被哈希到同一个键[如果您选择了一个好的哈希函数并且您没有太大的负载平衡。
  2. rehash 操作,即O(n),最多可以在n/2 操作之后发生,所有这些操作都假定为O(1):因此,当您对每个操作的平均时间求和时,您会得到:(n*O(1) + O(n)) / n) = O(1)

请注意,由于重新散列问题 - 实时应用程序和需要低 latency 的应用程序 - 不应使用散列表作为其数据结构。

编辑: 哈希表的另一个问题:cache
另一个您可能会在大型哈希表中看到性能损失的问题是由于缓存性能。 哈希表缓存性能不佳,因此对于大型集合 - 访问时间可能需要更长的时间,因为您需要将表的相关部分从内存重新加载回缓存。

【讨论】:

  • 谢谢-我想我明白了。因此,如果我在考试或面试中被要求提出一个在 O(1) 中执行查找的数据结构,你知道包含哈希表是否可以吗?
  • @user1136342:这取决于您需要最坏情况还是平均情况。对于一般情况,哈希表是O(1)。如果您需要最坏的情况 - 哈希表是不够的。
  • 维基百科说最坏的情况是can be reducedO(n)O(log n),方法是在每个桶中使用更复杂的数据结构。 (我想如果哈希表已经使用了良好的加密哈希,这可能被认为是矫枉过正,这甚至可以防止来自攻击者的冲突。)
  • @joeytwiddle 将排序数组作为辅助数据结构并不难,然后您确实可以保证 O(log(n)) 查找的最坏情况。还有其他哈希表可以保证 O(log(n)) 最坏情况的查找,例如hash ordering,并且通过使用大小为 n² 的完美哈希表作为辅助数据结构,您甚至可以保证 O(1) 最坏情况查找。
  • @codeexplorer No. Theta/big O/... 都是关于界限的 - 与您分析算法的方式无关。我试图在this thread 中解释一下。
【解决方案3】:

也许您正在研究空间复杂度?即 O(n)。其他复杂性与hash table 条目中的预期一致。随着桶数的增加,搜索复杂度接近 O(1)。如果在最坏的情况下,哈希表中只有一个桶,那么搜索复杂度是 O(n)。

根据评论进行编辑我认为说 O(1) 是一般情况是不正确的。它确实是(正如维基百科页面所说)O(1 + n / k),其中K是哈希表大小。如果 K 足够大,那么结果实际上是 O(1)。但是假设 K 为 10,N 为 100。在这种情况下,每个桶平均会有 10 个条目,所以搜索时间肯定不是 O(1);它是对多达 10 个条目的线性搜索。

【讨论】:

  • 哦- 我只是在看最坏的情况。所以要明确一点,当人们说 O(1) 时,他们只是指平均情况?
  • @user1136342:编辑了答案以试图澄清这一点。
  • 通常哈希表的load balancetable_size/8 <= #elements <= table_size/2,所以它又回到O(1)。但是,如果表的大小是动态的 - 仍然存在重新散列问题,这也是O(n) 的最坏情况。查看我的答案以获取详细信息和解释。
【解决方案4】:

理想情况下,哈希表是O(1)。问题是如果两个键不相等,但它们会产生相同的哈希。

例如,想象字符串 “it was the best of times it is the most of the times”“Green Eggs and Ham” 都产生了一个哈希值123.

当插入第一个字符串时,它被放入桶 123。当插入第二个字符串时,它会看到桶 123 的值已经存在。然后它将新值与现有值进行比较,并查看它们不相等。在这种情况下,将为该键创建一个数组或链表。此时,检索此值变为O(n),因为哈希表需要遍历该存储桶中的每个值以找到所需的值。

因此,在使用哈希表时,重要的是使用具有非常好的哈希函数的键,该函数既快速又不会经常导致不同对象的重复值。

有意义吗?

【讨论】:

  • as the hashtable needs to iterate through each value in that bucket 但是存储桶不包含 n 项目,只是那些散列到该特定键的项目?
  • 注意:可以使用平衡树代替链表来实现 lg(n) 检索,就像在 Java 8+ 中所做的那样。
  • @T.Rex:在最坏的情况下,存储桶将有 n 个项目
【解决方案5】:

取决于你如何实现散列,在最坏的情况下它可以达到 O(n),在最好的情况下它是 0(1)(如果你的 DS 不是那么容易,通常你可以实现)

【讨论】:

  • 如果你可以实现它使其成为 O(1),你为什么要实现它以使其成为 O(n)?
  • 好吧,我说的是最坏的情况
  • @JigarJoshi:你能举出最坏的例子来获得 O(n) 的运行时间吗?
  • 哈希函数返回单个数字,因此所有条目都在同一个桶中
猜你喜欢
  • 2016-08-29
  • 2019-05-10
  • 1970-01-01
  • 2014-05-25
  • 2012-05-21
  • 1970-01-01
  • 1970-01-01
  • 2020-11-19
  • 1970-01-01
相关资源
最近更新 更多