你要L什么,而且你有一个容器或者你有一个由q代器划分出来的区间——你要找的东西就在里面。你要怎么完成搜烦呢?你箭袋中的箭有这些:count、count_if、find、find_if、binary_search、lower_bound、upper_bound和equal_range。面对着它们Q你要怎么做出选择Q?/p>
单。你L的是能又快又单的东西。越快越单的好?/p>
暂时Q我假设你有一Ҏ定了搜烦区间的P代器。然后,我会考虑C有的是一个容器而不是一个区间的情况?/p>
要选择搜烦{略Q必M赖于你的q代器是否定义了一个有序区间。如果是Q你可以通过binary_search、lower_bound、upper_bound和equal_range来加速(通常是对数时间——参?a >条款34Q搜索。如果P代器q没有划分一个有序区_你就只能用线性时间的法count、count_if、find和find_if。在下文中,我会忽略掉count和find是否有_if的不同,像我会忽略掉binary_search、lower_bound、upper_bound和equal_range是否带有判断式的不同。你是依赖默认的搜烦谓词q是指定一个自qQ对选择搜烦法的考虑是一L?/p>
如果你有一个无序区_你的选择是count或着find。它们分别可以回{略微不同的问题Q所以值得仔细d分它们。count回答的问题是Q?#8220;是否存在q个|如果有,那么存在几䆾拯Q?#8221;而find回答的问题是Q?#8220;是否存在Q如果有Q那么它在哪儿?”
假设你想知道的东西是Q是否有一个特定的Widget值w在list中。如果用countQ代码看h像这P
list<Widget> lw; // Widget的list Widget w; // 特定的Widget? ... if (count(lw.begin(), lw.end(), w)) { ... // w在lw? } else { ... // 不在 }
q里C了一U惯用法Q把count用来作ؓ是否存在的检查。countq回零或者一个正敎ͼ所以我们把非零转化为true而把零{化ؓfalse。如果这栯使我们要做的更加显而易见:
if (count(lw.begin(), lw.end(), w) != 0) ...
而且有些E序员这样写Q但是用隐式{换则更常见,像最初的例子?/p>
和最初的代码比较Q用find略微更难懂些Q因Z必须查find的返回值和list的endq代器是否相{:
if (find(lw.begin(), lw.end(), w) != lw.end()) { ... // 扑ֈ? } else { ... // 没找? }
如果是ؓ了检查是否存在,countq个惯用法编码v来比较简单。但是,当搜索成功时Q它的效率比较低Q因为当扑ֈ匚w的值后find停止了Q而count必须l箋搜烦Q直到区间的l尾以寻扑օ他匹配的倹{对大多数程序员来说Qfind在效率上的优势以证明略微增加复杂度是合适的?/p>
通常Q只知道区间内是否有某个值是不够的。取而代之的是,你想获得区间中的W一个等于该值的对象。比如,你可能想打印个对象,你可能想在它前面插入什么,或者你可能惌删除它(但当q代时删除的引导参见条款9Q。当你需要知道的不止是某个值是否存在,而且要知道哪个对象(或哪些对象)拥有该|你就得用findQ?/p>
list<Widget>::iterator i = find(lw.begin(), lw.end(), w); if (i != lw.end()) { ... // 扑ֈ了,i指向W一? } else { ... // 没有扑ֈ }
对于有序区间Q你有其他的选择Q而且你应该明的使用它们。count和find是线性时间的Q但有序区间的搜索算法(binary_search、lower_bound、upper_bound和equal_rangeQ是Ҏ旉的?/p>
从无序区间迁Ud有序区间D了另一个迁U:从用相{来判断两个值是否相同到使用{h来判断?a >条款19׃个详l地讲述了相{和{h的区别,所以我在这里不会重复。取而代之的是,我会单地说明count和find法都用相等来搜索,而binary_search、lower_bound、upper_bound和equal_range则用{h?/p>
要测试在有序区间中是否存在一个|使用binary_search。不像标准C库中的(因此也是标准C++库中的)bsearchQbinary_search只返回一个boolQ这个值是否找C。binary_search回答q个问题Q?#8220;它在吗?”它的回答只能是是或者否。如果你需要比q样更多的信息,你需要一个不同的法?/p>
q里有一个binary_search应用于有序vector的例子(你可以从条款23中知道有序vector的优点)Q?/p>
vector<Widget> vw; // 建立vectorQ放?
... // 数据Q?
sort(vw.begin(), vw.end()); // 把数据排?
Widget w; // 要找的?
...
if (binary_search(vw.begin(), vw.end(), w)) {
... // w在vw?
} else {
... // 不在
}
如果你有一个有序区间而且你的问题是:“它在吗,如果是,那么在哪儿?”你就需要equal_rangeQ但你可能想要用lower_bound。我会很快讨论equal_rangeQ但首先Q让我们看看怎么用lower_bound来在区间中定位某个倹{?/p>
当你用lower_bound来寻找一个值的时候,它返回一个P代器Q这个P代器指向q个值的W一个拷贝(如果扑ֈ的话Q或者到可以插入q个值的位置Q如果没扑ֈQ。因此lower_bound回答q个问题Q?#8220;它在吗?如果是,W一个拷贝在哪里Q如果不是,它将在哪里?”和find一P你必L试lower_bound的结果,来看看它是否指向你要L的倹{但又不像findQ你不能只是lower_bound的返回值是否等于endq代器。取而代之的是,你必Llower_bound所标示出的对象是不是你需要的倹{?/p>
很多E序员这么用lower_boundQ?/p>
vector<Widget>::iterator i = lower_bound(vw.begin(), vw.end(), w); if (i != vw.end() && *i == w) { // 保证i指向一个对象; // 也就保证了这个对象有正确的倹{? // q是个bugQ? ... // 扑ֈq个|i指向 // W一个等于该值的对象 } else { ... // 没找? }
大部分情况下q是行得通的Q但不是真的完全正确。再看一遍检需要的值是否找到的代码Q?/p>
if (i != vw.end() && *i == w) ...
q是一?em>相等的测试,但lower_bound搜烦用的?em>{h。大部分情况下,{h试和相{测试生的l果相同Q但像条款19的,相等和等Ll果不同的情况ƈ不难见到。在q种情况下,上面的代码就是错的?/p>
要完全完成,你就必须lower_boundq回的P代器指向的对象的值是否和你要L的值等仗你可以手动完成Q?a >条款19演示了你该怎么做,当它值得一做时条款24提供了一个例子)Q但可以更狡猑֜完成Q因Z必须认使用了和lower_bound使用的相同的比较函数。一般而言Q那可以是一个Q意的函数Q或函数对象Q。如果你传递一个比较函数给lower_boundQ你必须认和你的手写的{h代码用了相同的比较函数。这意味着如果你改变了你传递给lower_bound的比较函敎ͼ你也得对你的{h部分作Z攏V保持比较函数同步不是火发,但却是另一个要C的东西,而且我想你已l有很多需要你记的东西了?/p>
q儿有一个简单的ҎQ用equal_range。equal_rangeq回一对P代器Q第一个等于lower_boundq回的P代器Q第二个{于upper_boundq回的(也就是,{h于要搜烦值区间的末P代器的下一个)。因此,equal_rangeQ返回了一对划分出了和你要搜烦的值等L区间的P代器。一个名字很好的法Q不是吗Q(当然Q也许叫equivalent_range会更好,但叫equal_range也非常好。)
对于equal_range的返回|有两个重要的地方。第一Q如果这两个q代器相同,意味着对象的区间是I的Q这个只没有扑ֈ。这个结果是用equal_range来回{?#8220;它在吗?”q个问题的答案。你可以q么用:
vector<Widget> vw; ... sort(vw.begin(), vw.end()); typedef vector<Widget>::iterator VWIter; // 方便的typedef typedef pair<VWIter, VWIter> VWIterPair; VWIterPair p = equal_range(vw.begin(), vw.end(), w); if (p.first != p.second) { // 如果equal_range不返? // I的区间... ... // 说明扑ֈ了,p.first指向 // W一个而p.second // 指向最后一个的下一? } else { ... // 没找刎ͼp.first? // p.second都指向搜索? } // 的插入位|?
q段代码只用{hQ所以L正确的?/p>
W二个要注意的是equal_rangeq回的东西是两个q代器,对它们作distanceq于区间中对象的数目,也就是,{h于要L的值的对象。结果,equal_range不光完成了搜索有序区间的dQ而且完成了计数。比如说Q要在vw中找到等价于w的WidgetQ然后打印出来有多少q样的Widget存在Q你可以q么做:
VWIterPair p = equal_range(vw.begin(), vw.end(), w);
cout << "There are " << distance(p.first, p.second)
<< " elements in vw equivalent to w.";
到目前ؓ止,我们所讨论的都是假设我们要在一个区间内搜烦一个|但是有时候我们更感兴于在区间中L一个位|。比如,假设我们有一个Timestampcd一个Timestamp的vectorQ它按照老的timestamp攑֜前面的方法排序:
class Timestamp { ... }; bool operator<(const Timestamp& lhs, // q回在时间上lhs const Timestamp& rhs); // 是否在rhs前面 vector<Timestamp> vt; // 建立vectorQ填充数据, ... // 排序Q老的旉 sort(vt.begin(), vt.end()); // 在新的前?
现在假设我们有一个特D的timestamp——ageLimitQ而且我们从vt中删除所有比ageLimit老的timestamp。在q种情况下,我们不需要在vt中搜索和ageLimit{h的TimestampQ因为可能不存在M{h于这个精值的元素?取而代之的是,我们需要在vt中找C个位|:W一个不比ageLimit更老的元素。这是再单不q的了,因ؓlower_bound会给我们{案的:
Timestamp ageLimit;
...
vt.erase(vt.begin(), lower_bound(vt.begin(), // 从vt中排除所?
vt.end(), // 排在ageLimit的?
ageLimit)); // 前面的对?
如果我们的需求稍微改变了一点,我们要排除所有至和ageLimit一栯的timestampQ也是我们需要找到第一个比ageLimitq轻的timestamp的位|。这是一个ؓupper_bound特制的Q务:
vt.erase(vt.begin(), upper_bound(vt.begin(), // 从vt中除L?
vt.end(), // 排在ageLimit的值前?
ageLimit)); // 或者等L对象
如果你要把东西插入一个有序区_而且对象的插入位|是在有序的{h关系下它应该在的地方Ӟupper_bound也很有用。比如,你可能有一个有序的Person对象的listQ对象按照name排序Q?/p>
class Person { public: ... const string& name() const; ... }; struct PersonNameLess: public binary_function<Person, Person, bool> { // 参见条款40 bool operator()(const Person& lhs, const Person& rhs) const { return lhs.name() < rhs.name(); } }; list<Person> lp; ... lp.sort(PersonNameLess()); // 使用PersonNameLess排序lp
要保持list仍然是我们希望的序Q按照nameQ插入后{h的名字仍然按序排列Q,我们可以用upper_bound来指定插入位|:
Person newPerson;
...
lp.insert(upper_bound(lp.begin(), // 在lp中排在newPerson
lp.end(), // 之前或者等?
newPerson, // 的最后一?
PersonNameLess()), // 对象后面
newPerson); // 插入newPerson
q工作的很好而且很方便,但很重要的是不要被误导——错误地认ؓupper_bound的这U用法让我们术般地在一个list里在Ҏ旉内找C插入位置。我们ƈ没有—?a >条款34解释了因为我们用了listQ查找花费线性时_但是它只用了Ҏơ的比较?/p>
一直到q里Q我都只考虑我们有一对定义了搜烦区间的P代器的情c通常我们有一个容器,而不是一个区间。在q种情况下,我们必须区别序列和关联容器。对于标准的序列容器Qvector、string、deque和listQ,你应该遵循我在本条款提出的徏议,使用容器的begin和endq代器来划分出区间?/p>
q种情况Ҏ准关联容器(set、multiset、map和multimapQ来说是不同的,因ؓ它们提供了搜索的成员函数Q它们往往是比用STL法更好的选择?a >条款44详细说明了ؓ什么它们是更好的选择Q简要地_是因为它们更快行为更自然。幸q的是,成员函数通常和相应的法有同L名字Q所以前面的讨论推荐你用的法count、find、equal_range、lower_bound或upper_boundQ在搜烦兌容器时你都可以简单的用同名的成员函数来代ѝ?/p>
调用binary_search的策略不同,因ؓq个法没有提供对应的成员函数。要试在set或map中是否存在某个|使用count的惯用方法来Ҏ员进行检:
set<Widget> s; // 建立setQ放入数?
...
Widget w; // w仍然是保存要搜烦的?
...
if (s.count(w)) {
... // 存在和w{h的?
} else {
... // 不存在这L?
}
要测试某个值在multiset或multimap中是否存在,find往往比count好,因ؓ一旦找到等于期望值的单个对象Qfind可以停下了Q而countQ在最遭的情况下,必须容器里的每一个对象。(对于set和mapQ这不是问题Q因为set不允讔R复的|而map不允讔R复的键。)
但是Qcountl关联容器计数是可靠的。特别,它比调用equal_range然后应用distance到结果P代器更好。首先,它更清晰Qcount 意味着“计数”。第二,它更单;不用建立一对P代器然后把它的组?strong>Q译注:是first和secondQ?/strong>传给distance。第三,它可能更快一炏V?/p>
要给出所有我们在本条ƾ中所考虑到的Q我们的从哪儿着手?下面的表格道Z一切?/p>
你想知道?/th> | 使用的算?/th> | 使用的成员函?/th> | ||
---|---|---|---|---|
在无序区?/td> | 在有序区?/td> | 在set或map?/td> | 在multiset或multimap?/td> | |
期望值是否存在? | find | binary_search | count | find |
期望值是否存在?如果有,W一个等于这个值的对象在哪里? | find | equal_range | find | find或lower_boundQ参见下面) |
W一个不在期望g前的对象在哪里? | find_if | lower_bound | lower_bound | lower_bound |
W一个在期望g后的对象在哪里? | find_if | upper_bound | upper_bound | upper_bound |
有多对象等于期望| | count | equal_rangeQ然后distance | count | count |
{于期望值的所有对象在哪里Q?/td> | findQP代) | equal_range | equal_range | equal_range |
上表ȝ了要怎么操作有序区间Qequal_range的出现频率可能o人吃惊。当搜烦Ӟq个频率因ؓ{h的重要性而上升了。对于lower_bound和upper_boundQ它很容易在相等中退_但对于equal_rangeQ只等h很自然的。在W二行有序区_equal_range打|了findq因Z个理由:equal_rangepҎ旉Q而findpU性时间?/p>
对于multiset和multimapQ当你在搜烦W一个等于特定值的对象的那一行,q个表列Zfind和lower_bound两个法作ؓ候选?已对于这个Q务find是通常的选择Q而且你可能已l注意到在set和map那一列里Q这只有find。但是对于multi容器Q如果不只有一个值存在,findq不保证能识别出容器里的{于l定值的W一个元素;它只识别q些元素中的一个。如果你真的需要找到等于给定值的W一个元素,你应该用lower_boundQ而且你必L动的对第二部分做{h,条款19的内容可以帮你确认你已经扑ֈ了你要找的倹{(你可以用equal_range来避免作手动{h,但是调用equal_range的花Ҏ调用lower_bound多得多。)
在count、find、binary_search、lower_bound、upper_bound和equal_range中做出选择很简单。当你调用时Q选择法q是成员函数可以l你需要的行ؓ和性能Q而且是最的工作。按照这个徏议做Q或参考那个表|Q你׃会再有困惑?/p>