【问题标题】:Binary search slower, what am I doing wrong?二进制搜索速度较慢,我做错了什么?
【发布时间】:2014-09-13 22:48:56
【问题描述】:

编辑:所以看起来这是正常行为,所以任何人都可以推荐一种更快的方法来完成这些众多的交叉路口吗?

所以我的问题是这样的。我有 8000 个列表(每个列表中的字符串)。对于每个列表(大小从 50 到 400),我将其与其他所有列表进行比较,并根据交叉点数执行计算。所以我会做的

list1(intersect)list1= number

list1(intersect)list2= number

list1(intersect)list888= number

我对每个列表都这样做。以前,我有 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 多秒(我只是停在那里,否则需要一分钟以上!!!)(这是数据的一小部分)

我确定某处存在严重错误,但我找不到。

【问题讨论】:

  • 您在该代码中的哪个位置对列表进行排序?还是您在此之前做过。
  • HashList 是 O(1) 摊销,BinarySearch() 是 O(logN)。所以这是完全正常的。
  • 嗨,是的,我在此之前做过
  • 真的,所以没有其他方法可以让它更快?
  • HashList 是要走的路。鉴于您正在进行大约 8000 x 4000 的列表比较(如果我理解正确的话),所以预计需要一段时间!如果你有一个固定的字符串列表,那么迭代所有这些可能会更快,但是考虑一下,我认为你上面的方法实际上会更快。不幸的是,我想不出其他加快速度的方法。

标签: c# performance list intersection


【解决方案1】:

我已经尝试了三种不同的方法来实现这一点(假设我正确理解了这个问题)。请注意,我使用HashSet&lt;int&gt; 是为了更轻松地生成随机输入。 设置:

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 秒

结论

您使用的方法是这三种方法中最快的。我当然想不出更快的单线程方式来做到这一点。也许有更好的并发解决方案。

【讨论】:

  • 我会试一试,因为我的结果只针对一小部分,我需要 8 分钟才能完成所有这些,哈哈。也许是因为我不是相交集合,而是对象的属性恰好是哈希集(我的集合是字符串,但我认为这并不重要)?如果我进行并行处理,我可以将每个交叉点的交叉点编号放在哪里?似乎如果我做一个矩阵,我将访问同一个矩阵,这会减慢它的速度。如果我创建一个具有 3 个属性 set1、set2 和 intersection_number 的对象怎么办?那么最终我将拥有该对象的 64,000 个实例?
  • 相交对象属性是否比仅获取这些属性并从中创建一个集合(如您的所有集合)并相交这些属性要慢?
【解决方案2】:

我发现在迭代/搜索任何类型的集合时,最重要的考虑因素之一是非常谨慎地选择集合类型。为您的目的遍历一个正常的集合并不是最理想的。尝试使用类似的东西:

System.Collections.Generic.HashSet<T>

在迭代两个较短的列表时使用 Contains() 方法(正如您提到的您已经在做的那样)应该提供接近 O(1) 的性能,与通用 Dictionary 类型中的键查找相同。

【讨论】:

  • 等等,我不是已经在使用 Hashsets,还是你的意思是不同的东西?
  • 你使用的是 List 类型
  • 是的,我使用的是 List>...?你能澄清一下或举个例子吗?
  • 好的,您的代码示例中没有显示您已经在使用 HashSet,或者在您的解释中对我很清楚。如果您已经这样做了,那么您需要考虑将 8000 个列表相互比较本身就是瓶颈。使用简单的循环不会达到最佳性能。考虑使用 System.Threading.Tasks.Parallel 类型来并行化您的计算。如果我有时间,将发布一个示例。
  • 谢谢,我确实调查过了,我在没有真正理解我在做什么的情况下实现了它,所以我只获得了 3 倍的速度提升,但我不知道如何处理这样一个事实我同时访问相同的变量,并且我想将交叉点编号存储在某处。现在,我认为我的效率低下导致中途出现内存不足异常(可能也是因为我的哈希表由字符串组成,而不是整数)。我应该在另一个问题中发布我更新的代码吗?
猜你喜欢
  • 2020-10-06
  • 2021-07-27
  • 1970-01-01
  • 1970-01-01
  • 2021-08-22
  • 2018-03-09
  • 1970-01-01
  • 2020-05-24
  • 1970-01-01
相关资源
最近更新 更多