C# 创建哈希集的最坏情况复杂性<;int>;收藏
我有一个C# 创建哈希集的最坏情况复杂性<;int>;收藏,c#,.net,complexity-theory,C#,.net,Complexity Theory,我有一个int值的集合,我用它以以下方式填充HashSet- var hashSet = new HashSet<int>(myIEnumerable); var hashSet=新hashSet(myIEnumerable); 假设迭代IEnumerable是O(n),那么以这种方式创建HashSet的最坏情况复杂度是多少?您可以通过在集达到最大大小时提供所有散列到同一个bucket的对象,将最坏情况带到O(n^2)。例如,如果传递一个17519ints的序列,该序列构造为 x
int
值的集合,我用它以以下方式填充HashSet
-
var hashSet = new HashSet<int>(myIEnumerable);
var hashSet=新hashSet(myIEnumerable);
假设迭代
IEnumerable
是O(n)
,那么以这种方式创建HashSet
的最坏情况复杂度是多少?您可以通过在集达到最大大小时提供所有散列到同一个bucket的对象,将最坏情况带到O(n^2)
。例如,如果传递一个17519int
s的序列,该序列构造为
x[i] = i * 17519
对于介于1和17519之间(含1和17519之间)的i
,在Microsoft实施HashSet
时,所有数字都将散列到初始存储桶,以O(N^2)
插入:
var h = new HashSet<int>(Enumerable.Range(1, 17519).Select(i => i*17519));
varh=newhashset(Enumerable.Range(117519)。选择(i=>i*17519));
设置呼吸点,并在调试器中检查
h
。查看原始视图/非公共成员/m_bucket。请注意,初始存储桶有17519个元素,而其余17518个元素都有零。文档实际上说明:
此构造函数是一个O(n)操作,其中n是
集合参数中的元素
对退化哈希代码(常数)的快速实验表明它是二次的
for(int n=0;n<100;n++)
{
var start=DateTime.UtcNow;
var s=new HashSet<Dumb>(Enumerable.Range(0,n*10000).Select(_=>new Dumb()));
Console.Write(n+" ");
Console.WriteLine((int)((DateTime.UtcNow-start).TotalSeconds*10));
}
现在有人声称,对于int,不会出现
HashCode
的多次冲突。虽然从技术上讲这是正确的,但影响性能的不是哈希代码的冲突,而是bucket索引的冲突。我认为HashSet
使用了类似于bucket=(hash&0x7FFFFFFF)%Capacity的东西。因此,如果您添加一个整数序列,它是首选存储桶大小的倍数,它仍然会非常慢。如果它是O(N^2),我不会感到惊讶,但是对于非摊销的最坏情况复杂性呢?如果您假设自定义时间的GetHashCode
,则可以强制执行比O(N^2)更坏的时间。例如,您可以有一个永远不会返回的GetHashCode
,永远无法完成任务,或者您可以有一个GetHashCode
方法,该方法需要O(n^2)
时间进行计算,因此,使HashSet
方法变得更糟糕。@Servy我的观点是,由于您无法控制Int32
的.NET的GetHashCode
,因此您无法强制将新的HashSet(myIEnumerable)
从OP放入O(N^2)
区域。当您控制GetHashCode
时,您可以强制HashSet
无限期地阻塞:)HashSet
是中间路线:您所能做的最糟糕的事情是O(N^2)
通过为.NET实现Int64.GetHashCode
提供一个特别糟糕的序列。对于int
s,您仍然可以创建bucket索引的冲突。只需添加容量倍数为的整数即可。在这种情况下,我期望O(n^2)加法性能,但我太懒了,无法计算出HashSet
的首选容量。但这是最坏情况下的复杂度还是摊销后的复杂度?@ughtsegment您的意思是“平均”复杂度,而不是“摊销”。“摊余”用于有时成本较高(例如支持存储翻倍)而其余成本较低的业务。这一概念与平均与最坏情况是正交的。要补充到CodeInChaos的答案中,它既是最坏的情况,也是分摊的复杂性。(考虑到他解释了为什么两者都有可能,我在这里说明的是实际情况。)不,一般来说,最坏的情况当然是二次的,但这适用于具有相同GetHashCode()输出的对象。我想知道int的情况。@JeppeStigNielsen我使用.NET Reflector来了解HashSet
如何获得它在散列计算中使用的模值。我使用这些信息向构造函数提供了各种值,这些值都属于同一个索引,在我的测试中,性能下降几乎是完全二次的。看起来最坏的情况下的复杂性实际上是O(n^2)
,即使哈希值中没有冲突。如果所有对象都返回相同的哈希代码,那么由于冲突,这就是O(n*n)。但是OP的问题是关于int的集合。所以我想知道选择一对具有相同哈希代码的int有多困难(可能?),我不认为您执行的测试与我在问题中描述的测试相同。我特别感兴趣的是将包含已知数量元素的集合传递给HashSet
构造函数的最坏情况复杂性,而不是多个Add
调用的复杂性。@SergeySint
是少数没有冲突的类型之一。可能的int
值的数量不大于可能的int
值的数量,因此int
值的哈希代码对于不同的值实际上是唯一的。(换句话说,它的散列码可以自己返回。)其他类型,如byte
和char
的值也小于int
,因此不会发生冲突。即使使用它,也可能导致桶索引发生冲突。完成任务更烦人@这和构造函数是一样的。请参阅更新的代码。
0 0
1 8
2 34
3 73
4 131