如何将Dictionary.Add方法分摊为O(1)?

时间:2013-04-16 10:36:24

标签: c# dictionary complexity-theory time-complexity

我试图从复杂的角度更好地理解哈希表和字典在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)复杂性?

2 个答案:

答案 0 :(得分:1)

但它不必检查所有键。它只需要检查散列到相同值的键。并且,正如您所说,一个好的哈希代码将最大限度地减少哈希冲突的数量,因此平均而言,它根本不需要进行任何密钥比较。

请注意,GetHashCode的规则如果a.HashCode <> b.HashCode,则为a <> b。但如果a.HashCode == b.GetHashCodea 可能等于 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); 

我不知道这个方法的执行情况是什么,但我认为它基于指针(所以内存中的地址)。