C++中的容器map和set

在C++标准模板库STL中将map和set归为关联式容器(associative container)。它们就像一个关联数组,把一个元素(key)映射到另一个元素(value),当然对于set来说,其value值就是key值。STL提供的关联式容器主要有set(集合)和map(映射表)两大类,以及他们的衍生类multiset和multimap,multiset和set也只是插入元素时的比较方式不同而已,底层实现基本相同。因为set和map的实现非常类似,所以它们总是成对出现,所以本文从更具代表性的map容器说起,说说那些在C++中可用的map容器。

map容器的底层实现主要有两种:基于(RB-tree)红黑树和基于hashtable(哈希表,散列表)。RB-tree能够确保一棵树中没有一条路径会比其他路径长2倍,它不仅是一棵搜索树,而且必须满足:

  1. 每个节点只能是红色或黑色;
  2. 根节点必须是黑色;
  3. 如果节点是红色,其子节点必须是黑色;
  4. 任一节点至树尾端(叶子节点)的任何路径,所含的黑色节点数必须相同。

这使得RB-tree的插入、删除以及查找的时间复杂度都为O(lgN),而且基于RB-tree的map,其键值是有序的(默认按字典序排列)。Hashtable能做到在平均常数时间内实现数据的插入、删除以及查找。Hashtable是一种典型的以空间换时间的设计。它将一个元素通过hash函数计算后,直接定址到其存储位置,hashtable的实际性能往往依赖于hash函数的设计,以及buckets(桶)的大小,当元素定址发生冲突时(两个元素经hash函数后被定址到同一个bucket中),通常采用开链法,即用链表将相同的元素链接在同一个bucket里。所以hashtable的最坏查找时间为O(N),而且基于hashtable的容器,其元素是无序的。

STL标准map容器,基于RB-tree

#include 
using namespace std;

map mk;

STL非标准map容器,基于hashtable,使用时通常要提供key比较的自定义比较函数,没有标准的map方便。

#include 
using namespace std;
//example
struct eqfunc 
{
    bool operator()(const char *str1,const char *str2)const
    {
        return strcmp(s1,s2)==0;
    }
};
/*
 *param1,param2,键值的类型
 *param3,键的hash算法
 *param4,键的比较函数
 */

hash_map<const char*,int,hash<const char*>,eqfunc > hsmap;

BOOST库unordered
基于hashtable的容器,无序的map,非标准容器,使用时需要安装boost库。

#include 
using namespace boost;

unordered_map mk;

C++11 unordered_map
基于hashtable的容器,无序的map,和boost库的unordered_map类似,但作为C11的标准容器

#include 
using namespace std;

ACE库的Map容器
ACE_Map_Manager被视为动态条目数组。每个条目有一个键值对组成。一旦动态数组满了,就会重新分配更大的内存,并将原有条目拷贝到新数组,同时释放原有数组。条目的删除采用惰性删除,其插入元素的最坏时间复杂度为O(N),读取操作总是线性的O(N)。ACE_Map_Manager还提供了第三个参数:锁类型,用以支持其上的线程安全操作。
ACE_Hash_Map_Manager相对于ACE_Map_Manager,其基于的是hashtable。
ACE_RB_Tree也可以实现map容器。

此外,基于一棵搜索二叉树,也可以实现map结构,例如之前介绍过基于Trie树Triemap结构。最后,在某些特定场景可以使用《编程珠玑》提及的Bitmap,Bitmap就是用一个bit位来标记某个元素对应的Value, 而Key即是该元素。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。当然随着算法的演进,如果1bit无法表述,可以使用2bit算法等。

另外对于容器的元素插入,推荐使用insert()方法,以减少不必要的开销。

#include 
#include 
using namespace std;

map<int,string> mi;
mi[1] = "One";//method 1
mi.insert(make_pair(1,"one"));////method 2

对于method 1,插入过程是,首先根据键值对做一个元素(value_type(1,”One”)),然后将这个元素插入到容器中,无论插入成功或失败(如果插入成功,则说明原来容器中不存在该元素;否则,原来容器中存在该元素,要更新)都会返回一个迭代器,指向被插入的元素,然后将新的元素的value值赋给迭代器指向的元素,这就带来了拷贝操作,如果value值是一个较为复杂的对象,这样势必会带来额外的开销。更推荐使用method 2。

文中只提及了一些可以使用map及其对应的头文件,至于具体的使用,可以参考相关的文档,在此不再累述。对于数据量较小的应用,推荐使用stl::map,如果对查找速度有特别要求,或者数据量巨大时,推荐使用C11标准的unordered_map,如果还没有开始使用C11,也可以使用boost的unordered_map。


《STL源码剖析》,侯捷。华中科技大学出版社。
《Boost程序库完全开发指南》,罗剑锋。电子工业出版社。
《ACE程序员指南:网络与系统编程的实用设计模式》,马维达译。中国电力出版社。

你可能感兴趣的:(C++)