代码改变世界

C++STL:Map剖析

2011-06-05 22:00  x_feng  阅读(796)  评论(0)    收藏  举报

map是(键-值)对的集合,也是通常所说的关联数组。可以使用键作为下标来获取一个值,关联的本质在于元素的值与某个特定的键相关联,而并非下标。

1,map简介

map是一类关联容器。它的特点是:增加和删除节点对迭代器影响很小,除了那个操作节点,对其它的节点都没什么影响。对于map来说,不可以修改键,但可以修改键对应值的值。map的底层结构是:平衡二叉搜索树,也就是RB-tree(红黑树)。

功能:

建立key-value的对应。key和value可以是任意类型。

根据key值快速查找记录,时间复杂度为log(N)。

快速插入key-value记录。

快速删除记录。

根据key修改value记录。

遍历。

2,map对象的定义

要使用map必须包含头文件#include <map>。必须指明键和值的类型:

map<string, int>my_map;my_map是一个Map的对象,键的类型是string,值的类型是int,注意map不允许相同的键存在,每个键都是不同的。定义一个map对象有3种方法:
map<key, value>m;
map<key, value>m2(m);
map<key, value>m2(begin, end);//这里的begin和end必须是指向另外一个map的迭代器。由于不能修改键,所以map的每一对记录key-value就是一个pair<const key, value>。

3,map插入数据,insert()函数

定义:map<string, int>my_map;

(1)my_map.insert(map<string, int>::value_type(“haha”, 1) );

(2)my_map.insert(make_pair(“lala”, 1) );

这两种方法都很简便。当然也可以定义一个pair<string, int>p;p.first = “yaya”;p.second = 5;my_map.insert(p)';这样也可以插入。

还有:my_map.insert(beg, end);my_map.insert(iter, e);

map可以用键来访问键对应的值:value = my_map[key];现在看看my_map[“haha”] = 2;会发生什么?

(1)在my_map中查找键haha,如果没有找到

(2)将这个新的键haha插入到my_map中,并对键haha初始化为0

(3)读取新插入的键haha,并将它的值赋为2

假如说我们想统计一篇文章的单词,并记录每个单词出现的次数,下面实现这个惊人的简练:

map<string, int>word_count;
string word;
while(cin >> word)
    ++word_count[word];//当然这样做效率不高,一次操作单词被查询2次。
那么insert()的返回值是什么呢?
my_map.insert(pair<string, int>);这种带有pair类型的参数返回一个pair型对象,pair的第一个值是my_map当前的迭代器,第二个值是bool,如果这个string已经存在了则返回false表示没有插入,这个key已经存在了,也不会修改key对应的value值。否则返回true表示插入成功。

现在我们再来看看上面的统计单词次数,为了提高效率,我们如下做:

map<string, int>word_count;
string word;
while(cin >> word)
{
    pair<map<string, int>::iterator, bool> ret = word_count.insert(make_pair(word, 1));
    if (!ret.second)//返回false表示键已有了,
        ++ret.first->second;//让这个单词的次数再加1
}//这样我们就判断了一次

4,map查找数据,count(),find()函数

map<string, int>my_map;
my_map.count(key);//查找这个键在不在,如果在就返回1,没有就返回0,这个函数只返回这两个值0或1
my_map.find(key);//查找这个键是否存在,如果有返回指向的key的迭代器,否则返回my_map.end()。

5,map删除数据,erase()函数

从map容器中删除元素的erase()操作有3种变化形式。与顺序容器一样,可向erase()传递一个或一对迭代器,来删除单个元素或一段范围内的元素。但有点不同:map容器的erase()操作返回void,而顺序容器的erase()操作返回一个迭代器,指向被删除元素后面的元素。

map<string, int>my_map;
my_map.erase(key);//通过关键字删除,这一种返回值是 key的个数,对于map是0或1
my_map.erase(iter);//通过迭代器删除,返回void
my_map.erase(beg, end);//通过迭代器删除一段范围,返回void
if (my_map.erase(key))//可以判断这个key是否存在,存在返回1,不在返回0

6,map底层数据结构

在说这个之前我们看看AVL tree:平衡二叉搜索树。满足一下条件:

1.任意节点大于它的左子树节点值,小于右子树节点值

2.任意节点的左右子树高度相差最多1。如下图:

无标题

与AVL-tree相似,另一被广泛使用的就是RB-tree树:平衡二叉搜索树(也称作红黑树)。除了满足AVL-tree的条件,还要满足一下规则:

1,每个节点不是红色就是黑色。

2,根节点为黑色。

3,如果节点为红,其子节点必须为黑。

4,任一节点至NULL(树尾端)的任何路径,所含之黑节点数必须相同。如下图:

无标题

其实map的底层就是RB-tree。

注:此图截取至《STL源码剖析》,如果你对map的底层结构感兴趣,可以详细查看这本书。