我在SO上看到一些有关Java哈希表及其O(1)
查找时间的有趣说法。有人能解释下为什么会这样吗?除非这些哈希表与我曾经接触过的任何哈希算法大不相同,否则必定存在包含冲突的数据集。
如果是这样,查找时间将会是O(n)
而不是O(1)
。
有人能解释一下它们是否真的是O(1),如果是,它们是如何做到的吗?
我在SO上看到一些有关Java哈希表及其O(1)
查找时间的有趣说法。有人能解释下为什么会这样吗?除非这些哈希表与我曾经接触过的任何哈希算法大不相同,否则必定存在包含冲突的数据集。
如果是这样,查找时间将会是O(n)
而不是O(1)
。
有人能解释一下它们是否真的是O(1),如果是,它们是如何做到的吗?
因此,即使只有少量元素的哈希表也很可能至少发生一次碰撞。大O符号允许我们做一些更具说服力的事情。注意到对于任意的、固定的常数k,都有:p碰撞 = n / 容量
我们可以利用这个特性来提高哈希表的性能。相反地,我们可以考虑最多发生2次碰撞的概率。O(n) = O(k * n)
这要低得多。由于处理一个额外碰撞的成本对于大O性能来说无关紧要,我们找到了一种在不实际更改算法的情况下提高性能的方法!我们可以将其推广到:p碰撞 x 2 = (n / 容量)2
现在,我们可以忽略一些任意数量的碰撞,以达到比我们考虑的更小的概率发生更多碰撞的程度。通过选择正确的k,您可以将概率降至任意小的水平,而无需更改算法的实际实现。p碰撞 x k = (n / 容量)k
hashCode
方法,分布可能不利,因此您的论证可能会失败。 - Ole V.V.您似乎混淆了散列表最坏情况下的运行时间与平均情况(期望)下的运行时间。一般来说,(不使用完美哈希)散列表的最坏情况确实是O(n),但在实际应用中这很少相关。
任何可靠的散列表实现,结合一个足够好的哈希函数,在期望情况下检索性能为O(1),非常小的因子(实际上是2),方差非常窄。
在Java中,HashMap是如何工作的?
hashCode
定位对应的桶[在桶容器模型内]。LinkedList
(或者从Java 8开始,在某些条件下是一个Balanced Red-Black Binary Tree
), 存储在该桶中的项目。equals
进行比较,逐一扫描这些项目。因此,有时需要与几个项目进行比较,但通常它比O(n) / O(log n)更接近于O(1)。
对于实际目的,这就是您需要知道的全部内容。
要记住的是,o(1)并不意味着每次查找只会检查单个项 - 它意味着每个容器中平均检查的项数相对于容器中项数保持恒定。因此,如果在具有100个项目的容器中平均需要4次比较来查找一个项目,则在具有10000个项目的容器中查找一个项目也应平均需要4次比较,并且对于任何其他项目数(总会有一些变化,尤其是在哈希表重新散列的点和当项目数量非常少时)。
因此,冲突并不会阻止容器具有o(1)操作,只要每个桶中键的平均数量保持在固定的范围内。
我知道这是一个老问题,但实际上它有一个新答案。
你是对的,哈希映射不算严格的 O(1)
,因为随着元素数量的任意增加,最终你将不能在常量时间内进行搜索(而O符号表示的是可以任意增大的数字)。
但并不意味着真正的时间复杂度是 O(n)
—— 因为没有规定桶必须实现成线性列表。
事实上,在Java 8中,一旦桶超过阈值,它们就会被实现为 TreeMaps
,这使得实际时间复杂度为 O(log n)
。
O(1+n/k)
其中 k
为桶的数量。
如果实现设置 k = n/alpha
那么它就是 O(1+alpha) = O(1)
因为 alpha
是一个常数。
java.util.HashMap
中,alpha
常量与构造函数中的 负载因子 参数相关。尽管不完全如此,因为 HashMap
在调整大小时选择大小的方式会使桶的数量 量化。(这种分析还假设键到桶的分布大致均匀;即它是一种 平均情况 复杂度,而不是 最坏情况 复杂度。) - Stephen C如果桶的数量(称为b)保持不变(通常情况),那么查找实际上是O(n)。
随着n的增大,每个桶中的元素数量平均为n/b。如果采用了常规的冲突解决方式(例如链表),则查找是O(n/b)=O(n)。
O表示的是当n变得越来越大时会发生什么。当应用于某些算法时,它可能会误导人,哈希表就是一个例子。我们选择桶的数量基于我们期望处理的元素数量。当n与b大小相当时,查找大致上是恒定时间,但我们不能称之为O(1),因为O是在n → ∞的极限意义下定义的。
HashMap中的元素被存储为链表(节点)数组。数组中的每个链表表示一个桶,用于存储一个或多个键的唯一哈希值。
在向HashMap中添加条目时,使用键的哈希码确定数组中桶的位置,类似于:
location = (arraylength - 1) & keyhashcode
这里的&表示按位与运算符。
例如:100 & "ABC".hashCode() = 64(键“ABC”的桶位置)
在get操作期间,它使用相同的方式确定键的桶位置。在最佳情况下,每个键都具有唯一的哈希码,并为每个键产生唯一的桶,在这种情况下,get方法仅花费时间来确定桶位置并检索值,这是常量O(1)。
在最坏的情况下,所有键都具有相同的哈希码并存储在同一个桶中,这导致遍历整个列表,从而导致O(n)。
在Java 8的情况下,如果大小增长到8个以上,则链表桶被TreeMap替换,这将最坏情况的搜索效率降低到O(log n)。
只有在哈希函数非常好的情况下,查找时间才为O(1)。Java哈希表实现并不防止使用较差的哈希函数。
无论添加项目时是否需要扩展表格都与问题无关,因为这涉及的是查找时间。