map是以平衡二叉树完成,其元素是 key/value pair;
map的是定义在std内的class templates:
template <class Key,class T,class Compare = less<key>,
class Allocator = allocator<pair<cosnt key,T>>>
class map;
其中:
- 第一个参数为key
- 第二个参数为value
- 第三个参数为排序准则
- 第四个参数为内存模型
map根据key自动进行排序,因此,根据某个已知key来搜寻某个元素,就有很好的性能;而根据已知value搜寻元素,性能就很糟糕。但是这也使得:不能直接改变元素key,因为这会改变元素的正确次序;所以,正如前面代码片段,key为const类型,而value则是常数可改;
与vector、list不同的是,我们可以定义map的排序准则,一般默认为less<>;相同的是,key/value也必须可赋值、可复制,对于排序准则而言,必须可比较;
定义排序规则
我们可以template参数定义排序规则:
std::map<folat,std::string,std::gerater<float> > coll;
但map也支持运行时修改排序规则,如下:
map c(op) //以op为排序准则,产生一个空的map/multimap
其中,op是一个函数对象,需要我们定义排序规则,在c++标准程序库一书中,提供了下面这个示例,以作参考:
#include <iostream>
#include <iomanip>
#include <map>
#include <string>
#include <algorithm>
using namespace std;
class RuntimeStringCmp{
public:
enum cmp_mode{normal,nocase};
private:
const cmp_mode mode;
static bool nocase_compare(char c1,char c2)
{
return toupper(c1) < toupper(c2);
}
public:
RuntimeStringCmp (cmp_mode m = normal) : mode(m){
}
bool operator()(const string& s1,const string& s2) const
{
if(mode == normal)
return s1 < s2;
else
return lexicographical_compare(s1.begin(),s1.end(),s2.begin(),s2.end(),nocase_compare);
}
};
typedef map<string,string,RuntimeStringCmp> StringStringMap;
void fillTheMap(StringStringMap coll);
int main()
{
StringStringMap coll;
fillTheMap(coll);
RuntimeStringCmp ignorecase(RuntimeStringCmp::nocase);
StringStringMap coll2(ignorecase);
}
代码并不难懂,唯一可能需要注意的可能是函数对象这个概念,不太理解的同学可以补补;
排序规则不同,但是pair类型相同,可以视作同一类型的map,可进行赋值,拷贝,互换,互换时排序规则也会互换;但是,不能用于比较;例如:
std::map<float,std::string> c1;
std::map<float,std::string,std::greater<float> > c2;
if(c1 == c2) { // ERROR :differenent types
...
}
元素查找
根据key值查找
我们可以使用STL提供的find()函数来进行根据key值得查找,但是为了更好的性能,map提供了成员函数find(),如下表所示:
根据value查找
正如前文所述,根据vale查找并没有根据value查找的良好性能,但是也无法避免存在这种使用的特殊场景,这种情况下,主要有两种方式:
- 遍历map,根据value进行符合条件的判断;
- 使用find_if,提供一个函数对象,根据value进行符合条件的判断。
元素排序
和list相同,map是双向迭代器,所以无法使用STL中用于随机存取迭代器(vector随机存取迭代器)中的算法;
元素修改
我们可以很轻易的修改value,无论是通过迭代器或者下标(返回引用)都可以,但是key值是const类型,该如何修改呢?
我们可以像下面这样修改key值:
std::map<std::string,float> coll;
coll['new _key"] = coll["old_key'];
coll.erase("olg_key");
上述过程可表述为:
元素移除安插
前文已知,map的key为const,这是因为map自动排序,而修改key值会打破这种排序,也因此,不能针对map调用任何变动性算法,如STL中的remove(),因为其实际是以一个参数覆盖被移除的元素;但我们可使用它们所提供的成员函数,例如erase()函数;
在这里,我们需要注意erase使用方式与list、vector的不同,这里的erase并不会返回下一个元素的迭代器位置,这是因为,这个过程对于map而言将会耗时,所以STL设计时就否决了这种设计,那么我们该如何使用迭代器移除元素呢?正确做法如下:
for(pos = coll.begin() ; pos != coll.end();)
{
if(pos->second == value )
coll.erase(pos++);
else
+pos;
}
pos++会移向下一元素,但返回其原始值的一个副本,因为erase被调用,但是pos已经不再指向那个即将被移除的元素了;
至于安插,我们可以使用insert函数,也可以使用map[key] = value直接进行赋值,但是需要注意的是,后者效率较低,因为其实际完成两个过程:新元素先用default构造函数将实值初始化,然后这个初值又马上被真正的value给覆盖了;
元素获取
map支持使用[]重载来获取元素,但需要注意的是,不是通过索引,而是key值;
其次,这种方式获取元素也存在一定风险,很多时候,我们在该key值已经存在的情况下可以直接获取,但是如果不存在呢?则会安插新元素,这在有时候可能会导致一些我们意象之外的问题;例如:
std::cout << coll[”otto“] ;
不知不觉间,我们已经为map新安插了一个键值为"otto",而value为0的元素;
map与multimap
map与multimap的唯一区别在于,multimap允许元素重复,map不允许。
map与unordered_map
- map内部实现了一个红黑树,红黑树具有自动排序的功能,因此map是有序的。unordered_map内部实现是哈希表,其元素排列是无序的。
- map内部实现红黑树,所以黑多操作在lgn的时间复杂度下就可以实现,因此效率非常高。
- unordered_map内部实现哈希表,因此其查找速度非常快,可达到O(1)。
- map空间占用率高,因为其每一个节点都需要额外保存父节点、子节点和红黑性质,使得每一个节点都占用大量空间。
- unordered_map中的哈希表建立比较费时。
对于那些对顺序有要求的问题,使用map更高效;而如果不关注有序,对查找效率要求比较高,那么应该使用unordered_map。至于其他操作,二者基本相同,这里不再赘述。