.NET集合提供最快的搜索


143

我有60k项需要对照20k的查找列表进行检查。是否有一个集合对象(如ListHashTable),提供了一个exceptionly快速Contains()的方法?还是我必须自己写?换句话说,默认Contains()方法是仅扫描每个项目,还是使用更好的搜索算法。

foreach (Record item in LargeCollection)
{
    if (LookupCollection.Contains(item.Key))
    {
       // Do something
    }
}

注意。查找列表已排序。


包含用于列表不适用于对象列表,因为它正在比较引用。
Fiur

2
排序数据?二进制搜索-参见@Mark的答案。
Hamish Smith,2009年

HashtTable在我的经验中击败了200万件物品
Chris S

顺便说一句,如果您的元素以有意义的顺序排列并且分布相当均匀,则可以通过使您的第一个猜测在项目的估计范围内来更快地执行二进制搜索。这对于您的特定应用可能没有任何意义。
布赖恩2009年

2
如果您想简化这些内容但避免使用哈希集,请不要忘记System.Collections.Generic.SortedList(TKey,TValue)。
布赖恩2009年

Answers:


141

在最一般的情况下,请考虑将其System.Collections.Generic.HashSet作为默认的“包含”主力数据结构,因为评估需要花费固定的时间Contains

“什么是最快的可搜索集合”的实际答案取决于您的特定数据大小,有序性,散列成本和搜索频率。


36
注意:不要忘记覆盖哈希码功能。为了提高性能,请在构造函数中预先生成哈希码。
布赖恩2009年

1
@Brian:好点。我当时(毫无根据地)假设Record.Key是某种内置类型。
吉米

3
@Brian:我宁愿第一次存储生成的内容,也不愿进行预生成,为什么用不知道是否会使用它的东西来减慢构造函数的速度呢?
jmservera

8
仅供参考:性能测试-我在字符串的List <T>和HashSet <T>之间创建了一个比较。我发现HashSet比List快1000倍。
Quango 2010年

10
@Quango:3年后,但实际上,如果您不指定数据集的大小,则此性能比较没有任何意义:哈希集具有O(1)搜索,列表具有O(n)搜索,因此性能比与。
克莱门特

73

如果您不需要订购,请尝试HashSet<Record>(.Net 3.5的新功能)

如果这样做,请使用List<Record>并致电BinarySearch


8
或者,在.NET> = 4中,使用SortedSet
StriplingWarrior 2012年

2
甚至更好,ImmutableSortedSet来自System.ImmutableCollections
Alexei S

24

你考虑过了List.BinarySearch(item)吗?

您说您的大型收藏集已经过整理,所以这似乎是绝佳的机会?哈希肯定是最快的,但这会带来自身的问题,并且需要更多的存储开销。


1
没错,使用可变对象作为键时,散列可能会带来一些不良问题。
jmservera

10

您应该阅读此博客该博客使用单线程和多线程技术对每种类型的集合和方法进行了快速测试。

根据结果​​,对List和SortedList的BinarySearch是在将某些内容作为“值”查找时经常并驾齐驱的最佳执行者。

当使用允许“键”的集合时,Dictionary,ConcurrentDictionary,Hashset和HashTables在整体上表现最佳。


4

保持列表x和y的排序顺序。

如果x = y,则执行操作;如果x <y,则前进x;如果y <x,则先y直到任一列表为空。

此交叉点的运行时间与最小值(大小(x),大小(y))成比例

不要运行.Contains()循环,这与x * y成正比,这更糟。


+1为更有效的算法。即使列表当前未排序,先对其进行排序然后再运行此算法也会更加有效。
Matt Boehm

但是,在最坏的情况下,运行时间是否与max(size(x),size(y))成正比?示例:int [] x = {99,100}; int [] y = {0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
马特·博姆

不能,因为一旦完成较小的集合,就可以追加来自较大集合的其余元素,因为它们已经被排序。我认为此过程类似于“合并排序”。

3

如果可以对项目进行排序,那么有一种更快的方法可以对哈希表或b树进行键查找。尽管如果您无法对物品进行排序,则无论如何都无法将它们真正放入b树中。

无论如何,如果对两个列表进行可排序排序,则只需按顺序遍历查找列表即可。

Walk lookup list
   While items in check list <= lookup list item
     if check list item = lookup list item do something
   Move to next lookup list item

是的,如此。如果您有两个排序列表,则只需要遍历一次即可。
丹佛,2015年

3

如果使用的是.Net 3.5,则可以使用以下方法制作更清晰的代码:

foreach (Record item in LookupCollection.Intersect(LargeCollection))
{
  //dostuff
}

我这里没有.Net 3.5,因此未经测试。它依赖于扩展方法。这并不是说LookupCollection.Intersect(LargeCollection)可能是不一样的LargeCollection.Intersect(LookupCollection)......,后者可能要慢得多。

假设LookupCollection是一个 HashSet


2

如果您不担心出现性能下降的情况,那么建议使用HashSet或二进制搜索。您的数据集还不够大,以至于99%的时间这将是一个问题。

但是,如果这只是您要执行的数千次,并且性能至关重要(并且使用HashSet /二进制搜索被证明是不可接受的),那么您当然可以编写自己的算法,以便在进行排序时遍历排序列表。每个列表最多可以遍历一次,并且在病理情况下也不错(一旦您走了这条路线,您可能会发现比较是假设实际花费,假设它是字符串或其他非整数值),优化将是下一步)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.