Answers:
HashMap的一个特殊功能是与平衡树不同,它的行为是概率性的。在这些情况下,就最坏情况发生的可能性而言,谈论复杂性通常是最有帮助的。对于散列图,当然是关于图恰好满了的情况。碰撞非常容易估计。
p 碰撞 = n /容量
因此,即使元素数量很少的哈希图也很可能会经历至少一次碰撞。大O表示法使我们可以做一些更具吸引力的事情。观察到任意任意的固定常数k。
O(n)= O(k * n)
我们可以使用此功能来改善哈希图的性能。我们可以考虑最多发生两次碰撞的可能性。
p 碰撞x 2 =(n /容量)2
这要低得多。由于处理一次额外碰撞的成本与Big O性能无关,因此我们找到了一种无需实际更改算法即可提高性能的方法!我们可以将其概括为
p 碰撞xk =(n /容量)k
现在,我们可以忽略任意数量的碰撞,并且最终产生的碰撞比我们所考虑的要少得多。通过选择正确的k,您可以将概率降低到任意微小的水平,而无需改变算法的实际实现。
我们通过说哈希映射很有可能具有O(1)访问来谈论此问题
您似乎将最坏情况的行为与平均情况(预期)的运行时混合在一起。对于散列表,前者的确确实是O(n)(即,不使用完美的散列),但实际上很少相关。
任何可靠的哈希表实现,加上半个体面的哈希,在预期的情况下,在非常窄的方差范围内,具有非常小的因数(实际上为2)的O(1)检索性能。
在Java中,HashMap通过使用hashCode来定位存储桶。每个存储桶是该存储桶中的项目列表。扫描项目,使用等于进行比较。添加项目时,一旦达到一定的加载百分比,将调整HashMap的大小。
因此,有时必须将其与几个项目进行比较,但通常它比O(n)更接近O(1)。出于实际目的,这就是您应该知道的所有内容。
我们已经建立了哈希表查找的标准描述为O(1)是指平均情况下的预期时间,而不是严格的最坏情况下的性能。对于通过链表解决冲突的哈希表(例如Java的哈希图),从技术上讲,它是O(1 +α),具有良好的哈希函数,其中α是表的负载因子。只要您存储的对象数不超过表大小的常数,该常数就保持不变。
也有人解释说,严格来说,可以为任何确定性哈希函数构造需要O(n)查找的输入。但是考虑最坏情况下的预期时间也很有趣,该时间不同于平均搜索时间。使用链时,这是O(1 +最长链的长度),例如,当α= 1时为Θ(log n / log log n)。
如果您对实现恒定时间预期的最坏情况查找的理论方法感兴趣,则可以阅读有关动态完美哈希的知识,该方法可以用递归方式解决与另一个哈希表的冲突!
HashMap内的元素存储为一个链表(节点)数组,该数组中的每个链表代表一个或多个键的唯一哈希值的存储桶。
在HashMap中添加条目时,键的哈希码用于确定存储桶在数组中的位置,例如:
location = (arraylength - 1) & keyhashcode
这里的&表示按位AND运算符。
例如: 100 & "ABC".hashCode() = 64 (location of the bucket for the key "ABC")
在获取操作期间,它使用相同的方法来确定钥匙的存储桶的位置。在最佳情况下,每个键都有唯一的哈希码,并为每个键生成唯一的存储桶,在这种情况下,get方法仅花费时间来确定存储桶的位置并检索常量O(1)的值。
在最坏的情况下,所有键都具有相同的哈希码并存储在相同的存储桶中,这导致遍历整个列表,从而导致O(n)。
在Java 8的情况下,如果大小增加到大于8,则将链表列表桶替换为TreeMap,这会将最坏情况的搜索效率降低到O(log n)。
这基本上适用于大多数编程语言中的大多数哈希表实现,因为算法本身并没有真正改变。
如果表中没有冲突,则只需进行一次查找,因此运行时间为O(1)。如果存在冲突,则必须进行多个查找,这会降低性能,趋向O(n)。
这取决于您选择避免冲突的算法。如果您的实现使用单独的链接,则最坏的情况是每个数据元素都被散列为相同的值(例如,散列函数选择不当)。在这种情况下,数据查找与对链表(即O(n))的线性搜索没有什么不同。但是,发生这种情况的可能性可以忽略不计,并且最佳查找和平均情况保持不变,即O(1)。
除了学者,从实践的角度看,HashMaps应该被认为具有不重要的性能影响(除非您的分析器告诉您其他情况)。
当然,哈希图的性能将取决于给定对象的hashCode()函数的质量。但是,如果实现该函数使得发生碰撞的可能性非常低,则它将具有很好的性能(在每种可能的情况下,严格来说,这不是严格的O(1),但在大多数情况下,情况)。
例如,Oracle JRE中的默认实现是使用随机数(该随机数存储在对象实例中,因此它不会改变-但它也禁用了偏向锁定,但这是另一个讨论),因此发生冲突的可能性是非常低。
hashCode % tableSize
这意味着肯定存在冲突。您没有充分利用32位。这就是哈希表的意义所在……您可以将较大的索引空间减小为较小的索引空间。