由于我使用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
答案 0 :(得分:7)
{macro-name}
是一种通用算法,给定一对迭代器可以找到一个值。如果给出的只是一对迭代器,那么查找值的最佳方法就是线性搜索O(n)。
std::find
是set::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)时间内获得start
和finish
之间的迭代器,如果没有这样的迭代器,则返回finish
存在。
在std::set
的树结构实现上,这样的操作实际上非常便宜。
但是此操作不存在。所以std::equal_range( begin(set), end(set) )
太慢了。
可能不公开诸如std::somewhere_between
之类的操作用于排序的关联容器,这会使某些set / map实现更加有效;许多以前使用特殊节点来替换一些叶子情况。也许您需要访问该特殊节点才能有效地对树进行二进制搜索。
但我严重怀疑缺少该操作是否值得。通过该操作,您可以有效地处理std::set
或std::map
子节;没有它,你什么都没有。