我需要访问大O表示法中post.php
的渐近时间和空间复杂度
所以我正在研究扩展方法Enumerable.Distinct
的实现,我看到它是使用内部类Set<T>
实现的,这几乎是一个带有“开放寻址”的哈希表的经典实现< / p>
Set<T>
中的大量代码只是来自HashSet<T>
的复制粘贴,但有一些遗漏
但是,这种简化的Set<T>
实施有一些明显的缺陷,例如Resize
方法不使用素数作为广告位的大小,例如{{3} },看看HashSet<T>
所以,我的问题是:
HashHelpers.ExpandPrime
原则?特别是考虑到这两个类都在同一个程序集IEnumerable.Distinct
HashSet<T>
代替HashSet<T>
吗?答案 0 :(得分:6)
这几乎是具有“开放寻址”的哈希表的经典实现
再看一遍。它与列表头单元格分开链接。当插槽全部在一个阵列中时,通过检查当前插槽的next
字段来查找碰撞情况下的下一个插槽。这比使用链接列表和每个节点作为单独的堆对象具有更好的缓存效率,但在这方面不如开放寻址那么好。同时,它避免了一些开放式寻址效果不佳的情况。
Set中的很多代码只是来自HashSet的复制粘贴,有一些遗漏
AFAICT使用哈希集的私有实现的原因是Enumerable
和HashSet
几乎在同一时间独立开发。这只是我的猜想,但它们都是用.NET 3.5引入的,所以它是可行的。
HashSet<T>
很可能是通过复制Set<T>
开始,然后让它更好地公开曝光,尽管这两者都可能都基于与列表头分开链接的相同原则细胞
就性能而言,HashSet
使用素数意味着它更有可能避免与较差的哈希冲突(但这只是一个优势,这不是一个简单的问题),但是{{1}在很多方面都很轻松,尤其是在.NET Core中,它不需要的东西被删除了。特别是,Set
的那个版本利用了这样一个事实:一旦项目被移除(例如,在Set
期间发生),将永远不会添加任何项目,这使得它可以省略Intersect
以及freelist
无法完成的与此相关的任何工作。即使最初的实施也没有跟踪版本以便在枚举期间捕获变化,这是一个很小的成本,但是每次添加和删除都会产生成本。
因此,对于具有不同哈希码分布的不同数据集,有时一个表现更好,有时表现更好。
特别是考虑到这两个类都在同一个程序集System.Core
中
仅在某些版本的.NET中,在某些版本中,它们位于不同的程序集中。在.NET Core中,我们有两个版本的HashSet
,一个在程序集中有Set<T>
,另一个在具有System.Linq
的单独程序集中。前者如上所述被削减,后者被System.Linq.Expressions
取代,因为它在那里做得少。
当然System.Core是第一位的,但是这些元素可以完全分开的事实说明System.Core不是一个单一依赖的整体blob。
.NET Core版本的Linq现在有一个HashSet<T>
方法,可以用ToHashSet()
替换Set<T>
更合理,但不是一件容易的事。我认为@james-ko正在考虑测试这样做的好处。
看起来
HashSet<T>
会表现得更好
由于上面解释的原因,情况可能并非如此,但可能确实如此,具体取决于源数据。这是在考虑经过一些不同的linq方法的优化之前(在linq的初始版本中并不多,但在.NET Core中很少)。
我应该避免使用
HashSet<T>
扩展方法,并编写自己的扩展方法,使用Distinct
代替HashSet<T>
。
使用Set<T>
。如果你有一个瓶颈,那么Distinct()
可能会赢得给定的数据集,但如果你这样做,请确保你的分析与你的代码在现实生活中会遇到的真实值非常接近。没有必要决定一种方法是基于某些任意测试更快,如果你的应用程序遇到另一个做得更好的情况。 (如果我发现这是一个问题点,我会先看看有问题类型的HashSet<T>
是否可以根据速度或位分布进行改进,首先是这样做。