为什么hashmap查找是O(1),即恒定时间?

时间:2013-03-18 04:31:29

标签: data-structures hash hashmap hashtable big-o

如果我们从Java的角度来看,那么我们可以说hashmap查找需要不变的时间。但内部实施呢?对于不同的匹配键,它仍然必须搜索特定的桶(对于哪个键的哈希码匹配)。那么为什么我们说hashmap查找需要恒定的时间?请解释一下。

4 个答案:

答案 0 :(得分:32)

在对正在使用的哈希函数的适当假设下,我们可以说哈希表查找需要预期的 O(1)时间(假设您正在使用标准哈希方案,如线性探测或链接散列)。这意味着平均,哈希表执行查找的工作量最多只是一些常量。

直观地说,如果你有一个“好”的哈希函数,你会期望元素在整个哈希表中或多或少地均匀分布,这意味着每个桶中的元素数量将接近于分割的元素数量按桶数量。如果哈希表实现保持这个数字很低(比如,每次元素与桶的比率超过某个常数时添加更多桶),那么完成的预期工作量最终会成为选择哪个桶的基线工作量应该进行扫描,然后进行“不要太多”工作,查看那里的元素,因为期望在那个桶中只有恒定数量的元素。

这并不意味着哈希表具有保证 O(1)行为。实际上,在最坏的情况下,散列方案将退化,并且所有元素将最终在一个桶中,使查找在最坏的情况下花费时间Θ(n)。这就是设计好哈希函数的重要原因。

有关更多信息,您可能需要阅读算法教科书以查看为什么哈希表如此有效地支持查找的正式推导。这通常作为典型的算法和数据结构大学课程的一部分,在线有很多好的资源。

有趣的事实:有一些类型的哈希表(cuckoo哈希表,动态完美哈希表),其中元素的最坏情况查找时间是O(1)。这些哈希表的工作原理是保证每个元素只能位于几个固定位置中的一个位置,插入有时会在元素周围加扰以试图使一切都适合。

希望这有帮助!

答案 1 :(得分:7)

关键在于文档中的声明:

  

如果要将多个映射存储在HashMap实例中,那么使用足够大的容量创建映射将允许映射更有效地存储,而不是根据需要执行自动重新散列来扩展表。

  

负载系数衡量哈希表在其容量自动增加之前可以获得多长。当哈希表中的条目数超过加载因子和当前容量的乘积时,哈希表将被重新哈希(即,重建内部数据结构),以便哈希表具有大约两倍的桶数。 / p>

http://docs.oracle.com/javase/6/docs/api/java/util/HashMap.html

如果超出加载因子,实际上将重建内部存储桶结构,从而允许获取摊销成本放到O(1)。

请注意,如果内部结构被重建,则会引入可能为O(N)的性能损失,因此可能会有很多 get put 摊销成本再次接近O(1)之前需要。因此,请适当规划初始容量和负载系数,这样既不会浪费空间,也不会触发内部结构的可避免重建。

答案 2 :(得分:2)

哈希表不是 O(1)。

通过鸽巢原理,您无法比 O(log(n)) 更好地查找,因为您需要每个项目的 log(n) 位来唯一标识 n 个项目。

哈希表似乎是 O(1),因为它们有一个小的常数因子,再加上它们在 O(log(n)) 中的“n”增加到了这一点,对于许多实际应用应用程序,它与您使用的实际项目数量无关。然而,大 O 表示法并不关心这个事实,并且(当然,非常普遍)滥用表示法来调用哈希表 O(1)。

因为虽然您可以在哈希表中存储一百万或十亿个项目,并且仍然获得与单个项目哈希表相同的查找时间...如果您处理大约一百万或 googleplex 项目,您将失去这种能力。对于大 O 表示法,您永远不会真正使用 nonillion 或 googleplex 项目这一事实无关紧要。

实际上,哈希表的性能可能比数组查找性能差一个常数因子。是的,这也是 O(log(n)),因为你不能做得更好。

基本上,现实世界的计算机使每个数组查找大小小于其芯片位大小的数组与其理论上可用的最大数组一样糟糕,而且由于 hastables 是对数组执行的巧妙技巧,这就是为什么您看起来< /em> 得到 O(1)

答案 3 :(得分:0)

跟进templatetypedef的评论:

哈希表的常量时间实现可以是一个哈希映射,您可以使用该哈希映射实现一个布尔数组列表,该列表指示存储桶中是否存在特定元素。但是,如果要为hashmap实现链接列表,最糟糕的情况是需要遍历每个存储桶并且必须遍历列表的末尾。