二进制搜索更慢,我做错了什么
本文关键字:错了 什么 搜索 二进制 | 更新日期: 2023-09-27 18:21:50
EDIT:所以看起来这是正常的行为,所以有人能推荐一种更快的方法来完成这些众多的交叉吗?
所以我的问题是这个。我有8000个列表(每个列表中都有字符串)。对于每个列表(大小从50到400),我将其与其他列表进行比较,并根据交叉点编号进行计算。所以我会做
list1(相交)list1=数字
list1(相交)list2=编号
list1(相交)list888=编号
我对每个列表都这样做。以前,我有HashList,我的代码本质上是这样的:(嗯,我实际上是在搜索对象的属性,所以我不得不对代码进行一点修改,但基本上是这样的:
我下面有两个版本,但如果有人知道得更快,请告诉我!
循环浏览AllLists,获取每个列表,从list1开始,然后执行以下操作:
foreach (List list in AllLists)
{
if (list1_length < list_length) //just a check to so I'm looping through the
//smaller list
{
foreach (string word in list1)
{
if (block.generator_list.Contains(word))
{
//simple integer count
}
}
}
// a little more code, but the same, but looping through the other list if it's smaller/bigger
然后,我将列表制作成常规列表,并应用Sort(),这将我的代码更改为
foreach (List list in AllLists)
{
if (list1_length < list_length) //just a check to so I'm looping through the
//smaller list
{
for (int i = 0; i < list1_length; i++)
{
var test = list.BinarySearch(list1[i]);
if (test > -1)
{
//simple integer count
}
}
}
第一个版本大约需要6秒,另一个版本需要20多秒(我只是停在那里,否则会需要一分钟以上!!)(这是针对数据的一小部分)
我确信某个地方有一个严重的错误,但我找不到。
我已经尝试了三种不同的方法来实现这一点(假设我正确理解了这个问题)。请注意,我使用HashSet<int>
是为了更容易地生成随机输入。设置:
List<HashSet<int>> allSets = new List<HashSet<int>>();
Random rand = new Random();
for(int i = 0; i < 8000; ++i) {
HashSet<int> ints = new HashSet<int>();
for(int j = 0; j < rand.Next(50, 400); ++j) {
ints.Add(rand.Next(0, 1000));
}
allSets.Add(ints);
}
我检查了三种方法(代码在内部循环中运行):
循环:
请注意,您在代码中得到了重复的结果(将集合A
与集合B
相交,稍后将集合B
与集合A
相交)。由于您正在进行列表长度检查,它不会影响您的性能。但以这种方式迭代更清晰。
for(int i = 0; i < allSets.Count; ++i) {
for(int j = i + 1; j < allSets.Count; ++j) {
}
}
第一种方法:
使用IEnumerable.Intersect()
来获得与其他列表的交集,并检查IEnumerable.Count()
来获得交集的大小。
var intersect = allSets[i].Intersect(allSets[j]);
count = intersect.Count();
这是平均177秒中速度最慢的一次
第二种方法:
克隆了我相交的两个集合中的较小集合,然后使用ISet.IntersectWith()
并检查生成的集合Count
。
HashSet<int> intersect;
HashSet<int> intersectWith;
if(allSets[i].Count < allSets[j].Count) {
intersect = new HashSet<int>(allSets[i]);
intersectWith = allSets[j];
} else {
intersect = new HashSet<int>(allSets[j]);
intersectWith = allSets[i];
}
intersect.IntersectWith(intersectWith);
count = intersect.Count;
}
}
这一次稍快,平均154秒
第三种方法:
做了一些与您在较短集合上迭代并在较长集合上检查ISet.Contains
非常相似的事情。
for(int i = 0; i < allSets.Count; ++i) {
for(int j = i + 1; j < allSets.Count; ++j) {
count = 0;
if(allSets[i].Count < allSets[j].Count) {
loopingSet = allSets[i];
containsSet = allSets[j];
} else {
loopingSet = allSets[j];
containsSet = allSets[i];
}
foreach(int k in loopingSet) {
if(containsSet.Contains(k)) {
++count;
}
}
}
}
这种方法是迄今为止最快的(正如预期的那样),平均为66秒
结论
你使用的方法是这三种方法中最快的。我当然想不出比这更快的单线程方法了。也许还有更好的并发解决方案。
我发现,在迭代/搜索任何类型的集合时,最重要的考虑因素之一是非常小心地选择集合类型。为了您的目的而遍历一个普通集合并不是最理想的。尝试使用以下内容:
System.Collections.Generic.HashSet<T>
使用Contains()方法同时迭代较短的两个列表(正如您已经提到的那样)应该会获得接近O(1)的性能,与通用Dictionary类型中的键查找相同。