map是一种关联式容器,值以
键值对 "pair" 的形式储存
,STL 的 map 以红黑树作为地底层结构。
概述
map中的元素以键值对 (pair) 的形式被储存。键值对的第一元素 first
为键值, 第二元素 second
为实值,第一元素键值不允许被改变,原因是 map
内部元素依据键值排序,若改变键值会影响 map
的结构。而第二元素可以被改变。
由于红黑树是一棵平衡二叉查找树,在查找上拥有 O(LogN)
的时间复杂度,故以它作为红黑树的底层结构最合适不过了。
map结构
现在以我们自己设计的红黑树为底层结构模拟实现一个 map
。
下面是红黑树的部分源代码(详细代码点击此处,红黑树介绍点这)
//红黑树主体
template<class Key, class Value, class _KeyOfValue = __KeyOfValue<Value>>
class RBTree
{
typedef __RBTreeNode<Value> Node;
//插入
/*
返回值是一个键值对,若插入成功其第一元素为插入节点的迭代器,第二元素为 true, 若插入失败(说明树中已经存在要插得那个节点),则返以存在节点的迭代器,第二元素为 false
*/
pair<Iterator, bool> Insert(const Value& value)
{...}
protected:
Node *_header;//红黑树头结点
以上述的红黑树为底层结构,现设计的 map
如下:
template<class Key, class Value>
class Map
{
struct __KeyOfPair//从ValueType取得键值的方法(仿函数)
{
Key operator()(const pair<Key, Value> kv){
return kv.first;
}
};
typedef pair<const Key, Value> ValueType;//真正储存在红黑树节点中的值,是一个键值对
typedef RBTree<Key, ValueType, __KeyOfPair> RepType;//容器类型
//...
public:
pair<Iterator, bool> Insert(const ValueType& kv)
{
return _t.Insert(kv);
}
Iterator Find(const Key& key)
{
_t.Find(key);
}
//下面才是map的特色
Value& operator[](const Key& key)
{
return (_t.Insert(make_pair(key, Value())).first)->second;
}
//...
private:
RepType _t;
如上是我们自己实现的 map
。可以看到几乎所有操作都只是简单调用了红黑树的接口,但不同的是, map
增加了一个接口: operator[]
,这个才是 map
最大的特色。
map的使用
对于数组我们可以这样使用:
int a[10];
a[0] = 10;
a[1] = 20;
//...
它最大的特点就是,可以在 O(1)
的时间复杂度上实现对数组任意下标位置数据的访问,哈希表中的直接定址法就是利用这种思想。
其实换个角度想想,我们可以认为“数组里的值是以键值对的形式储存”,键值对的第一元素(first
)为下标,即 a[1] = 10;
中的下标 1
,它不可以被改变,键值对的第二元素为该下标中储存的实值,即 a[1] = 10;
中的 10
,但这种形式有个弊端——下标的类型只能是固定的 size_t
。而map解决了这种弊端,只不过牺牲了一点时间(它对数据访问的时间复杂度为 O(LogN)
)。请看下面:
如上图所示,我们可以像访问数组那样去访问 map
,而且它的下标可以是任意类型。
下面我们看看它的实现代码:
Value& operator[](const Key& key)
{
return (_t.Insert(make_pair(key, Value())).first)->second;
}
理解:
- map 重载
operator[]
实现像数组那样通过下标访问元素;- 底层调用红黑树的
insert()
接口,尝试插入key
,该接口返回一个键值对,若插入成功键值对第一元素为插入节点的迭代器,第二元素为 true, 若插入失败(说明树中已经存在要插得那个节点),则返已存在节点 (即key
) 的迭代器,第二元素为 false。- 由上我们得出,
(_t.Insert(make_pair(key, Value())).first)
,该句先调用红黑树的插入接口,其返回一个键值对,再通过.first
取得键值对的第一元素,即被插入节点的迭代器;(_t.Insert(make_pair(key, Value())).first)->second
, 再通过->second
取得迭代器的第二元素,这次取得的元素是map
的实值;- 而
operator[]
返回值是Value
类型的引用, 即map
实值的引用,通过这个引用就可以改变其实值了;- 总结上述,通过下标
key
可以访问到其对应的实值value
,并且可以被改变,是不是和数组一样了。
其它插入方法法:
(以下以VS2013库中的为例)
调用 insert()
接口,但重点是对待插入元素的构造,提供以下几种构造方法。
对于其它的“删改查”操作,则是直接调用底层红黑树的接口。
【作者:果冻 http://blog.csdn.net/jelly_9】
——完!