我试图从复杂的角度更好地理解哈希表和字典在C#中是如何工作的(但我猜这种语言不是一个重要的因素,这可能只是一个理论问题)。
我知道如果Add
小于容量(这是显而易见的),Dictionary
的方法Count
应该是O(1)。
但是,让我们看一下代码:
public class Foo {
public Foo() { }
public override int GetHashCode() {
return 5; //arbitrary value, purposely a constant
}
}
static void Main(string[] args) {
Dictionary<Foo, int> test = new Dictionary<Foo,int>();
Foo a = new Foo();
Foo b = new Foo();
test .Add(a, 5);
test .Add(b, 6); //1. no exception raised, even though GetHashCode() returns the same hash
test .Add(a, 10); //2. exception raised
}
我理解比在幕后有1.
的哈希碰撞,可能还有一个单独的链接来处理它。
但是,在2.
处引发了参数异常。这意味着在内部字典保留了在确定其哈希值后插入的每个键的跟踪。这也意味着每次我们在字典中添加一个条目时,它会使用equals
方法检查是否尚未插入密钥。
我的问题是,如果它检查已经插入的密钥似乎应该是O(n),为什么它被认为是O(1)复杂性?
答案 0 :(得分:1)
但它不必检查所有键。它只需要检查散列到相同值的键。并且,正如您所说,一个好的哈希代码将最大限度地减少哈希冲突的数量,因此平均而言,它根本不需要进行任何密钥比较。
请注意,GetHashCode
的规则如果a.HashCode <> b.HashCode
,则为a <> b
。但如果a.HashCode == b.GetHashCode
,a
可能等于 b
。
另外,你说:
我知道如果Count小于容量(这是显而易见的),方法Add的字典应该是O(1)。
这不完全正确。那就是理想,假设一个完美的哈希函数,它将为每个键提供一个唯一的数字。但是在一般情况下,完美的哈希函数不存在,所以通常你会看到O(1)(或非常接近它)的性能,直到Count超过一个相当大的容量百分比:比如85%或90%
答案 1 :(得分:0)
答案简单而困难。 简单部分:这是因为(你可以自己检查)
a.Equals(b) == false
如果在添加“b”时需要例外,只需实现Equals方法。
没有困难的部分: Equals的默认对象实现调用RuntimeHelpers.Equals。 RuntimeHelpers的来源是here。不幸的是,这个方法是extern:
[System.Security.SecuritySafeCritical] // auto-generated
[ResourceExposure(ResourceScope.None)]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public new static extern bool Equals(Object o1, Object o2);
我不知道这个方法的执行情况是什么,但我认为它基于指针(所以内存中的地址)。