一、总体介绍
在count、find、binary_search、lower_bound、upper_bound和equal_range中做出选择很简单。当你调用时,选择算法还是成员函数可以给你需要的行为和性能,而且是最少的工作。按照这个建议做(或参考上面表格),你就不会再有困惑。
二、count、find、binary_search、lower_bound、upper_bound和equal_range的用法
你要寻找什么,而且你有一个容器或者你有一个由迭代器划分出来的区间——你要找的东西就在里面。面对着它们,你要怎么做出选择?
一) 暂时,我假设你有一对指定了搜索区间的迭代器,并打算在区间中搜索一个值
1、假如是无序区间、你的选择是count或着find,
count回答的问题是:“是否存在这个值,如果有,那么存在几份拷贝?”
而find回答的问题是:“是否存在,如果有,那么它在哪儿?”
1)举例:判断有没有期望的值
假设你想知道的东西是,是否有一个特定的Widget值w在list中。
如果用count,代码看起来像这样:
// Widget的list特定的Widget值
list<Widget> lw;
Widget w;
...
if (count(lw.begin(), lw.end(), w)) {
// w在lw中
...
} else {
// 不在
...
}
这里示范了一种惯用法:把count用来作为是否存在的检查。count返回零或者一个正数,所以我们把非零转化为true而把零转化为false。
使用find略微更难懂些,因为你必须检查find的返回值和list的end迭代器是否相等:
if (find(lw.begin(), lw.end(), w) != lw.end()) {
// 找到了
...
} else {
// 没找到
...
}
如果是为了检查是否存在,count这个惯用法编码起来比较简单。但是,当搜索成功时,它的效率比较低,因为当找到匹配的值后find就停止了,而count必须继续搜索,直到区间的结尾以寻找其他匹配的值。对大多数程序员来说,find在效率上的优势足以证明略微增加复杂度是合适的。
2)举例:找出拥有期望值的对象
当你需要知道的不止是某个值是否存在,而且要知道哪个对象(或哪些对象)拥有该值,你就得用find:
list<Widget>::iterator i = find(lw.begin(), lw.end(), w);
if (i != lw.end()) {
// 找到了,i指向第一个
...
} else {
// 没有找到
...
}
对于有序区间,你有其他的选择,而且你应该明确的使用它们。count和find是线性时间的,但有序区间的搜索算法(binary_search、lower_bound、upper_bound和equal_range)是对数时间的。
2、假如是有序区间
从无序区间迁移到有序区间导致了另一个迁移:从使用相等来判断两个值是否相同到使用等价来判断。count和find算法都用相等来搜索,而binary_search、lower_bound、upper_bound和equal_range则用等价。
1)举例:判断有没有期望的值
要测试在有序区间中是否存在一个值,使用binary_search。
binary_search回答这个问题:“它在吗?”它的回答只能是是或者否。
// 建立vector,放入数据,把数据排序
vector<Widget> vw;
...
sort(vw.begin(), vw.end());
// 要找的值
Widget w;
...
if (binary_search(vw.begin(), vw.end(), w)) {
// w在vw中
...
} else {
// 不在
...
}
2) 判断是不是有期望的值,并指出 拥有期望值的对象的位置?
(1)lower_bound的使用
lower_bound回答这个问题:“它在吗?如果是,第一个拷贝在哪里?如果不是,它将在哪里?”
当你用lower_bound来寻找一个值的时候,它返回一个迭代器,这个迭代器指向这个值的第一个拷贝(如果找到的话)或者到可以插入这个值的位置(如果没找到)。
// 保证i指向一个对象;也就保证了这个对象有正确的值。
vector<Widget>::iterator i = lower_bound(vw.begin(), vw.end(), w);
if (i != vw.end() && *i == w) {
//这是个bug! 找到这个值,i指向第一个等于该值的对象
...
} else {
// 没找到
...
}
大部分情况下这是行得通的,但不是真的完全正确。f (i != vw.end() && *i == w) ...这是一个相等的测试,但lower_bound搜索用的是等价。大部分情况下,等价测试和相等测试产生的结果相同,相等和等价的结果不同的情况并不难见到。
(2)equal_range的使用
equal_range返回一对迭代器,第一个等于lower_bound返回的迭代器,第二个等于upper_bound返回的(也就是,等价于要搜索值区间的末迭代器的下一个)。因此,
equal_range,返回了一对划分出了和你要搜索的值等价的区间的迭代器。
对于equal_range的返回值,有两个重要的地方。
第一,如果这两个迭代器相同,就意味着对象的区间是空的;这个只没有找到。这个结果是用equal_range来回答“它在吗?”这个问题的答案。
第二个要注意的是equal_range返回的东西是两个迭代器,对它们作distance就等于区间中对象的数目,也就是,等价于要寻找的值的对象。完成了计数。回答了“有多少?”这个问题的答案
//有没有?
vector<Widget> vw;
...
sort(vw.begin(), vw.end());
// 方便的typedef
typedef vector<Widget>::iterator VWIter;
typedef pair<VWIter, VWIter> VWIterPair;
VWIterPair p = equal_range(vw.begin(), vw.end(), w);
if (p.first != p.second) {
// 如果equal_range不返回空的区间,说明找到了
// p.first指向第一个,而p.second指向最后一个的下一个
...
} else {
// 没找到,p.first和 p.second都指向搜索值的插入位置
...
}
这段代码只用等价,所以总是正确的。
//期望值有多少?
VWIterPair p = equal_range(vw.begin(), vw.end(), w);
cout << "There are " << distance(p.first, p.second) << " elements in vw equivalent to w.";
二) 一对指定了搜索区间的迭代器,在区间中寻找一个位置
1)lower_bound和 upper_bound的使用
(1)找到位置删除元素
假设我们有一个Timestamp类和一个Timestamp的vector,它按照老的timestamp放在前面的方法排序:
class Timestamp { ... };
// 返回在时间上lhs是否在rhs前面
bool operator<(const Timestamp& lhs, const Timestamp& rhs);
// 建立vector,填充数据,
vector<Timestamp> vt;
...
// 排序,使老的时间在新的前面
sort(vt.begin(), vt.end());
假设我们有一个特殊的timestamp——ageLimit,而且我们从vt中删除所有比ageLimit老的timestamp。在这种情况下,我们不需要在vt中搜索和ageLimit等价的Timestamp,因为可能不存在任何等价于这个精确值的元素。
取而代之的是,我们需要在vt中找到一个位置:第一个不比ageLimit更老的元素。这是再简单不过的了,因为lower_bound会给我们答案的:
Timestamp ageLimit;
...
// 从vt中排除所有排在ageLimit的值前面的对象
vt.erase( vt.begin(), lower_bound(vt.begin(), vt.end(), ageLimit) );
如果我们的需求稍微改变了一点,我们要排除所有至少和ageLimit一样老的timestamp,也就是我们需要找到第一个比ageLimit年轻的timestamp的位置。这是一个为upper_bound特制的任务:
// 从vt中除去所有排在ageLimit的值前面或者等价的对象
vt.erase( vt.begin(), upper_bound(vt.begin(),vt.end(), ageLimit) );
(2)找到位置插入元素
如果你要把东西插入一个有序区间,而且对象的插入位置是在有序的等价关系下它应该在的地方时,upper_bound也很有用。比如,你可能有一个有序的Person对象的list,对象按照name排序:
class Person {
public:
...
const string& name() const;
...
};
struct PersonNameLess : public binary_function<Person, Person, bool> {
bool operator()(const Person& lhs, const Person& rhs) const {
return lhs.name() < rhs.name();
}
};
list<Person> lp;
...
// 使用PersonNameLess排序lp
lp.sort(PersonNameLess());
要保持list仍然是我们希望的顺序(按照name,插入后等价的名字仍然按顺序排列),我们可以用upper_bound来指定插入位置:
Person newPerson;
...
// 在lp中排在newPerson之前或者等价的最后一个对象后面插入newPerso
lp.insert( upper_bound( lp.begin(), lp.end(), newPerson, PersonNameLess() ), newPerson);
三)只给一个容器,而不是一个区间
1、对于标准序列容器,使用容器的begin和end迭代器来划分出区间,在执行上面的步骤
2、对于标准关联容器,幸运的是,成员函数通常和相应的算法有同样的名字,所以前面的讨论推荐你使用的算法count、find、equal_range、lower_bound upper_bound,在搜索关联容器时你都可以简单的用同名的成员函数来代替。
1)测试在set或map中是否存在某个值
// 建立set,放入数据
set<Widget> s;
...
// w仍然是保存要搜索的值
Widget w;
...
if (s.count(w)) {
// 存在和w等价的值
...
} else {
// 不存在这样的值
...
}
2)测试某个值在multiset或multimap中是否存在
find往往比count好,因为一旦找到等于期望值的单个对象,find就可以停下了,而count,在最遭的情况下,必须检测容器里的每一个对象。
count给关联容器计数是可靠的。特别,它比调用equal_range然后应用distance到结果迭代器更好。
首先,它更清晰:count 意味着“计数”。
第二,它更简单;不用建立一对迭代器然后把它的组成(就是first和second)传给distance。
第三,它可能更快一点。