在最坏的情况下,单个查找操作或contains
可以是O(n)
吗?因此,对于n
元素,hashSet
中的查找将是O(n^2)
?
答案 0 :(得分:61)
是的,但这确实是最糟糕的情况:如果HashSet
中的所有元素都具有相同的哈希码(或通向同一个桶的哈希码)。使用正确编写的hashCode
和正态分布的密钥样本,查找为O(1)。
答案 1 :(得分:6)
是的,但我们拥有HashSets的全部原因是我们遇到这种最坏情况的概率非常非常低,并且它通常比保证nlogn对于堆或(自我平衡)TreeSet要快得多,或者保证n ^ 2表示未排序的列表。
答案 2 :(得分:0)
正如在前面的答案中已经指出的,查找时间复杂度为 O(1)。为了确保它是真的,只需查看 contains()
的源代码:
...
private transient HashMap<E,Object> map;
...
public boolean contains(Object o) {
return map.containsKey(o);
}
...
如您所见,它在内部使用 HashMap
对象来检查您的对象是否存在。
然后,如果我们查看 contains()
的 HashMap
实现,那么我们将看到如下代码:
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
getNode()
根据键哈希值和键值搜索节点。请注意,hash(key)
的时间复杂度为 O(1)。
最后,对于getNode()
:
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab;
Node<K,V> first, e;
int n;
K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != 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;
}
最重要的部分基本上是第一个内部 if
块:
...
if (first.hash == hash &&
((k = first.key) == key || (key != null && key.equals(k))))
return first;
...
如果对象 key
的哈希值和第一个元素 first
的哈希值相等,并且对象本身也相等(显然!),那么 first
就是我们要查找的对象对于,这是 O(1)。
如你所见,这一切都取决于哈希函数的实现——如果它很好,那么它主要会为不同的键对象分配不同的桶。如果不是,那么多个键对象可能驻留在同一个存储桶中,因此我们需要在存储桶本身中进行查找以找到正确的键,如下所示:
...
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);
但是,即使在这种情况下,如果您的存储桶是 TreeNode
,它也是 O(log(k))(k - 存储桶中的元素数),因为它是一个平衡的二叉搜索树。如果不是(else
块),则为 O(k)。但同样,这种情况很少发生(对于某些类型的对象甚至可能永远不会发生),因此一次调用 contains
方法的平均时间复杂度将保持为 O(1)。显然,如果您执行 n
调用,则总时间复杂度将是线性的。
答案 3 :(得分:-17)
lookp需要O(c)
c =常数值