二进制搜索速度较慢,我做错了什么?

时间:2014-09-13 22:48:57

标签: c# performance list intersection

编辑:所以看起来这是正常的行为,所以有人能推荐一种更快的方式来做这些众多的交叉点吗?

所以我的问题是这个。我有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秒(我只是停在那里,否则它将花费超过一分钟!!!)(这是一小部分数据)

我确定某处有一个严重的错误,但我无法找到它。

2 个答案:

答案 0 :(得分:1)

我已经尝试了三种不同的方法来实现这一点(假设我正确理解了问题)。请注意我已使用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();

这是平均177s的最慢的

第二种方法:

克隆了我正在交叉的两组中较小的一组,然后使用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;
    }
}

这个稍快一点,平均154s

第三种方法:

做了一些非常类似于你在较短集上迭代的内容,并检查了较长集上的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秒

结论

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

答案 1 :(得分:0)

我发现迭代/搜索任何类型的集合中最重要的考虑因素之一是非常仔细地选择集合类型。为您的目的迭代正常集合将不是最佳的。尝试使用类似的东西:

System.Collections.Generic.HashSet<T>

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