对于set <int> s,为什么std :: find(s.begin(),s.end(),val)比s.find(val)慢1000倍?

时间:2018-08-28 20:47:20

标签: c++ find set std

由于我使用C ++进行编码已有十多年了,所以我最近开始学习C ++。即使在SGI工作时,我也很少使用STL,我想掌握它。我已经订购了一本书,目前正在运行不同的在线教程。

一个教程介绍了std::find(begin(),end(),value),我对编写的测试代码这么慢感到震惊。经过反复试验,我发现s.find(value)显然是我应该使用的。

为什么代码中的第一个发现是如此缓慢?

set<int> s;

for (int i = 0; i < 100000; i++)
    s.insert(rand());

for (int i = 0; i < 10000; i++) {
    int r = rand();

    //first find is about 1000x slower than the next one
    auto iter1 = std::find(s.begin(), s.end(), r);
    auto iter2 = s.find(r);
}

编辑:添加了计时实验结果

@juanchopanza询问评论的时间安排,因此我将std::find()设置为Set,List,Vector和set.find()的时间 (我只测量发现-运行之间的差异低于10%)

Vector的性能比“列表”或“集合”要好得多,但是从集合中进行的专门发现会赢得大数据集。

 Elements  Vector     List      Set    | Set.Find()
      10   0.0017    0.0017    0.0020  |  0.0017
     100   0.0028    0.0051    0.0120  |  0.0019
    1000   0.0105    0.0808    0.1495  |  0.0035
   10000   0.0767    0.7486    2.7009  |  0.0068
  100000   0.2572    2.4700    6.9636  |  0.0080
 1000000   0.2674    2.5922    7.0149  |  0.0082
10000000   0.2728    2.6485    7.0833  |  0.0082

3 个答案:

答案 0 :(得分:7)

{macro-name}是一种通用算法,给定一对迭代器可以找到一个值。如果给出的只是一对迭代器,那么查找值的最佳方法就是线性搜索O(n)。

std::findset::find的成员函数,因此它知道要搜索的数据结构,因此可以优化搜索。经过排序的平衡树具有极好的O(log(n))搜索行为

答案 1 :(得分:4)

扩大我的评论。

因为set::find具有有关搜索范围内元素的更多信息。它知道(可能)实现为排序的二叉树,并且可以在对数时间内搜索它。

另一方面,

std::find仅获得两个双向迭代器,因此,它能做的最好的事情基本上只是一个for循环。 如果该集合返回了 random-access 迭代器,std::find也将是对数的。 编辑:更正了我的错误主张。

答案 2 :(得分:2)

第一个原因是根据线性搜索指定了std::find。同时,std::set.find是根据对数时间搜索指定的。

但是如果您将std::find替换为std::equal_range(将执行二进制搜索),则会发现它的运行速度与std::find一样慢。

所以我会回答比您问的更好的问题:

为什么std::equal_range在集合迭代器上速度很慢?

嗯,真的没有什么理由。

std::set迭代器是双向迭代器。这意味着它们允许前进一级或后退一级。

双向迭代器上的

std::equal_range 非常慢,因为它必须逐步遍历范围。

另一方面,std::set.find方法使用std::set的树结构来真正快速地找到元素。基本上,它可以非常快地获得范围的中点。

当您通过其迭代器访问std::set时,C ++不会公开此树结构。如果有的话,可能存在像std::somewhere_between( start, finish )这样的操作,它将在O(1)时间内获得startfinish之间的迭代器,如果没有这样的迭代器,则返回finish存在。

std::set的树结构实现上,这样的操作实际上非常便宜。

但是此操作不存在。所以std::equal_range( begin(set), end(set) )太慢了。

可能不公开诸如std::somewhere_between之类的操作用于排序的关联容器,这会使某些set / map实现更加有效;许多以前使用特殊节点来替换一些叶子情况。也许您需要访问该特殊节点才能有效地对树进行二进制搜索。

但我严重怀疑缺少该操作是否值得。通过该操作,您可以有效地处理std::setstd::map子节;没有它,你什么都没有。