在C#.NET中,我喜欢使用HashSets,因为它们的查找时间复杂度为O(1)。如果我要查询大量数据,我通常更喜欢将HashSet用于List,因为它具有这种时间复杂度。
令我困惑的是HashSet的构造函数,它将IEqualityComparer作为参数:
http://msdn.microsoft.com/en-us/library/bb359100.aspx
在上面的链接中,备注注意到“构造函数是一个O(1)操作”,但如果是这种情况,我很好奇,如果查找仍然是O(1)。
特别是,在我看来,如果我要写一个Comparer来传递给HashSet的构造函数,每当我执行查找时,必须在每个键上执行Comparer代码以检查以查看如果有匹配。这不是O(1),而是O(n)。
当元素添加到集合中时,实现是否在内部构建查找表?
一般来说,我如何确定有关.NET数据结构复杂性的信息?
答案 0 :(得分:19)
HashSet
通过哈希(通过IEqualityComparer.GetHashCode
)对您插入的对象进行处理,并根据哈希将对象放入存储桶中。桶本身存储在阵列中,因此是O(1)部分。
例如(这不一定是C#实现的工作方式,它只是给出了一种风格)它接受散列的第一个字符,并将带有以1开头的散列的所有内容抛入存储桶1.散列2,存储桶2 , 等等。在该桶中是另一个桶阵列,它们通过哈希中的第二个字符进行分配。那么对于哈希中的每个字符......
现在,当你向上看时,它会散列它,然后跳过适当的桶。它必须进行多次数组查找(哈希中每个字符一次)但不会随着N的增加而增长,N是您添加的对象数,因此是O(1)等级。
对于您的另一个问题,这是一篇博客文章,其中包含许多馆藏操作的复杂性:http://c-sharp-snippets.blogspot.com/2010/03/runtime-complexity-of-net-generic.html
答案 1 :(得分:14)
如果我要写一个Comparer来传递给HashSet的构造函数,每当我执行查找时,必须在每个键上执行Comparer代码以检查是否存在匹配。这不是O(1),而是O(n)。
让我们调用您正在搜索“查询”值的值。
你能解释为什么你认为必须在每个键上执行比较器以查看它是否与查询匹配?
这种看法是错误的。 (除非比较器提供的哈希码对于每个键都是相同的!)搜索算法在每个键上执行相等比较器,其键入与查询的哈希码匹配,以桶的数量为模在哈希表中。这就是散列表获得O(1)查找时间的方式。
当元素添加到集合中时,实现是否在内部构建查找表?
是。
一般来说,我如何确定有关.NET数据结构复杂性的信息?
阅读文档。
答案 2 :(得分:1)
这取决于您的GetHashCode()
实现提供的哈希函数(IEqualityComparer
)的质量。理想的散列函数应该提供分布均匀的散列码随机集。这些哈希码将用作允许将键映射到值的索引,因此通过键搜索值变得更有效,尤其是当键是复杂对象/结构时。
必须在要检查的每个密钥上执行Comparer代码 看看是否有匹配。这不是O(1),而是O(n)。
这不是哈希表的工作原理,这是一种直接的暴力搜索。在哈希表的情况下,你会有更智能的方法,它使用索引搜索(哈希码)。
答案 3 :(得分:1)
如果传递IEqualityComparer,查找仍为O(1)。哈希集仍然使用与不传递IEqualityComparer时相同的逻辑;它只使用IEqualityComparer的GetHashCode和Equals实现,而不是System.Object的实例方法(或者有问题的对象提供的覆盖)。
答案 4 :(得分:1)
实际上,HashSet<T>
的查找时间并不总是O(1)。
正如其他人已经提到的那样,HashSet使用IEqualityComparer<T>.GetHashCode()
。
现在考虑始终返回相同哈希码x
的结构或对象。
如果将n个项目添加到HashSet中,则将有n个具有相同哈希值的项目(只要对象不相等)。
因此,如果您要检查HashSet中是否存在带有哈希代码x
的元素,它将对所有带有哈希代码x
的对象进行相等性检查,以测试HashSet是否包含元素