HashMap#replace 的复杂度是多少?
我想知道replace(Key , Value)for a HashMapis的复杂性是什么。
我最初的想法是O(1)因为它是O(1)获取值,我可以简单地替换分配给键的值。
我不确定是否应该考虑在 java 中实现的大型哈希图中可能存在的冲突java.util。
回答
电话:博士
HashMap#replace已O(1) 摊销;
并且在地图适当平衡的前提下,Java 在您的put和remove调用期间负责,也未摊销。
未摊销
它是否也适用于非摊销分析取决于有关实施的自平衡机制的问题。
基本上,由于replace只改变值不影响散列和HashMap中的一般结构,替换值将不会触发任何重新散列或内部结构的重新组织。
因此,我们只支付定位 的成本key,这取决于桶的大小。
桶大小,如果地图适当地自平衡,可以被认为是一个常数。从而O(1)为replace也非摊销。
但是,该实现仅基于启发式因素触发自平衡和重新散列。对此的深入分析有点复杂。
因此,由于启发式方法,现实可能介于两者之间。
执行
可以肯定的是,让我们看看当前的实现(Java 16):
@Override
public V replace(K key, V value) {
Node<K,V> e;
if ((e = getNode(key)) != null) {
V oldValue = e.value;
e.value = value;
afterNodeAccess(e);
return oldValue;
}
return null;
}
该方法afterNodeAccess是子类的虚拟方法,并且在HashMap. 除了微不足道的getNode运行之外的一切O(1)。
getNode
getNode是在 a 中定位条目的规范实现HashMap,我们知道它运行在O(1)适当的自平衡映射中,例如 Java 实现。让我们来看看代码:
/**
* Implements Map.get and related methods.
*
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n, hash; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & (hash = hash(key))]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
此方法基本上计算 hash hash = hash(key),然后在 中查找 hashtable first = tab[(n - 1) & (hash = hash(key))]并开始迭代存储在存储桶中的数据结构。
关于桶的数据结构,我们在if (first instanceof TreeNode).
桶
存储桶要么是简单的隐式链表,要么是红黑树。
链表
对于链表,我们有一个简单的迭代
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
这显然O(m)与m链表的大小有关。
红黑树
对于红黑树,我们有
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
在红黑树中查找是O(log m),m是树的大小。
铲斗尺寸
Javas 实现确保在检测到失控时通过重新散列来重新平衡存储桶(您为每个修改方法(如put或remove)支付费用)。
因此,在这两种情况下,我们都可以将桶的大小视为常数,或者由于涉及自平衡的启发式方法,接近常数。
结论
具有固定大小的水桶,有效,使得getNode运行O(1),导致replace在运行O(1)也是如此。
如果没有任何自平衡机制,在最坏的情况下,它会降级为O(n)如果使用链表和O(log n)红黑树(对于所有键都产生哈希冲突的情况)。
随意深入研究代码,但它在那里变得有点复杂。