容器搜索算法的选择

一、总体介绍


在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。

第三,它可能更快一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值