一. 简介
- map是STL 的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。
- map内部自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的,后边我们会见识到有序的好处。
- map 是一种有序无重复的关联容器。关联容器与顺序容器不同,他们的元素是按照关键字来保存和访问的,而顺序元素是按照它们在容器中的位置保存和访问的。map保存的是一种 key - value 的pair对象,其中 key 是关键字,value 是关键字对应的值。通过 key找到对应的 value。map中按照 key的大小升序排列pair对象。map会自动建立Key - value的对应。
二. 用法
1. 头文件
#include <map>
2. 构造函数
//在定义map对象时,必须分别指明键和值的类型。Map建立key-value的一种映射。
//创建一个名为m的空map对象,其键和值的类型分别为key和value
map<key, value> m;
//创建m2的副本m,m与m2必须有相同的键类型和值类型
map<key, value> m(m2);
//创建map类型的对象m,存储迭代器b和e标记的范围内所有元素的副本,元素的类型必须能转换为pair
map<key, value> m(b,e);
//comp可选,键值对存放策略
map<key, value, comp> mp;
3. map定义的类型
// 在map容器中,用作索引的键的类型
map<key, value> ::key_type;
// 在map容器中,键所关联的值的类型
map<key, value> ::mapped_type;
// 一个pair类型,它的first元素具有const map<key, value> ::key_type类型,而second元素则xmap<key, value> :: mapped_type类型。
map<key, value> ::value_type;
在学习map接口时,谨记其value_type是pair类型,它的值成员可以修改(second成员),但键成员不能修改。这个value_type相当于map的元素类型,而不是键所对应的值的类型。
对map迭代器进行解引用将产生pair类型的对象,它的first成员存放键,为const,而second成员存放值。
4. 插入数据
①用insert函数插入pair数据
m.insert(pair<int, string>(1, "student_one"));
make_pair()//返回类型为对应的pair类型
无需写出类别,就可以生成一个pair对象
例:
make_pair(1,'@')
而不必费力的写成
pair<int ,char>(1,'@')
②用insert函数插入value_type数据
m.insert(map<int, string>::value_type (1, "student_one"));
③数组插入方式
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent[1] = "student_one";
mapStudent[2] = "student_two";
mapStudent[3] = "student_three";
map<int, string>::iterator iter;
for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
{
cout<<iter->first<<" "<<iter->second<<endl;
}
}
5. map的大小
m.size()查看
6. 数据的遍历
①应用前向迭代器
例如4-③中的例子
②应用反向迭代器
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
map<int, string>::reverse_iterator iter;
for(iter = mapStudent.rbegin(); iter != mapStudent.rend(); iter++)
{
cout<<iter->first<<" "<<iter->second<<endl;
}
}
③用数组方式
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
int nSize = mapStudent.size()
//此处有误,应该是 for(int nIndex = 1; nIndex <= nSize; nIndex++)
//by rainfish
for(int nIndex = 0; nIndex < nSize; nIndex++)
{
cout<<mapStudent[nIndex]<<end;
}
}
7. 数据的查找
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
map<int, string>::iterator iter;
iter = mapStudent.find(1);
if(iter != mapStudent.end())
{
cout<<"Find, the value is "<<iter->second<<endl;
}
Else
{
cout<<"Do not Find"<<endl;
}
}
8. 排序
#include <map>
#include <string>
uing namespace std;
Typedef struct tagStudentInfo
{
int nID;
String strName;
}StudentInfo, *PStudentInfo; //学生信息
int main()
{
int nSize;
//用学生信息映射分数
map<StudentInfo, int>mapStudent;
map<StudentInfo, int>::iterator iter;
StudentInfo studentInfo;
studentInfo.nID = 1;
studentInfo.strName = "student_one"
mapStudent.insert(pair<StudentInfo, int>(studentInfo, 90));
studentInfo.nID = 2;
studentInfo.strName = "student_two";
mapStudent.insert(pair<StudentInfo, int>(studentInfo, 80));
for (iter=mapStudent.begin(); iter!=mapStudent.end(); iter++)
cout<<iter->first.nID<<endl<<iter->first.strName<<endl<<iter->second<<endl;
}
以上程序是无法编译通过的,只要重载小于号,就OK了,如下:
Typedef struct tagStudentInfo
{
int nID;
String strName;
Bool operator < (tagStudentInfo const& _A) const
{
//这个函数指定排序策略,按nID排序,如果nID相等的话,按strName排序
If(nID < _A.nID) return true;
If(nID == _A.nID) return strName.compare(_A.strName) < 0;
Return false;
}
}StudentInfo, *PStudentInfo; //学生信息
三. multimap
- multimap 与 map 一样,都是使用红黑树对记录型的元素数据,按元素键值的比较关系,进行快速的插入、删除和检索操作。
- 所不同的是 multimap 允许将具有重复键值的元素插入容器。在 multimap 容器中,元素的键值与元素的映照数据的映照关系,是多对多的,因此,multimap 称为多重映照容器。
- multimap 与 map 之间的多重特性差异,类似于 multiset 与 set 的多重特性差异。
- multimap 多重映照容器实现了 Sorted Associative Container 、Pair Associative Container 和 Multimap Associative Container 概念的接口规范。
- 一些说明:
/*
*
********************************************
* multimap多重映照容器的基础说明:
********************************************
*
* multimap多重映照容器:容器的数据结构采用红黑树进行管理
* multimap的所有元素都是pair:第一元素为键值(key),不能修改;第二元素为实值(value),可被修改
*
* multimap特性以及用法与map完全相同,唯一的差别在于:
* 允许重复键值的元素插入容器(使用了RB-Tree的insert_equal函数)
* 因此:
* 键值key与元素value的映照关系是多对多的关系
* 没有定义[]操作运算
*
* Sorted Associative Container Pair Associative Container Unique Associative Container
*
* 使用multimap必须使用宏语句#include <map>
*
*/
/*************创建multimap对象**************/
multimap<char,int,greater<char> > a;
//元素键值类型为char,映照数据类型为int,键值的比较函数对象为greater<char>
multimap(const key_compare& comp);
//指定一个比较函数对象comp来创建map对象
multimap(const multisetr&);
//multimap<int,char*> b(a); //此时使用默认的键值比较函数less<int>
multimap(first,last);
multimap(first,last,const key_compare& comp);
//Example:
pair<const int ,char> p1(1,'a');
pair<const int ,char> p2(2,'b');
pair<const int ,char> p3(3,'c');
pair<const int ,char> p4(4,'d');
pair<const int ,char> pairArray[]={p1,p2,p3,p4};
multimap<const int,char> m4(pairArray,pairArray+5);
multimap<const int,char> m3(m4);
multimap<const int,char,greater<const int> > m5(pairArray,pairArray+5,greater<const int>());
/**************元素的插入****************/
typedef pair<const key,T> value_type;
pair<iterator,bool> insert(const value_type& v);
iterator insert(iterator pos,const value_type& v);
void insert(first,last);
/*********元素的删除*************/
void erase(iterator pos);
size_type erase(const key_type& k);
//删除等于键值k的元素
void erase(first,last);
//删除[first,last)区间的元素
void clear();
/*********访问与搜索**************/
iterator begin();iterator end();
//企图通过迭代器改变元素是不被允许的
reverse_iterator rbegin();
reverse_iterator rend();
iterator find(const key_type& k) const;
pair<iterator,iterator> equal_range(const key_type& k) const;//返回的pair对象,
//first为lower_bound(k);大于等于k的第一个元素位置
//second为upper_bound();大于k的第一个元素位置
/*********其它常用函数*************/
bool empty() const;
size_type size() const;
size_type count(const key_type& k) const; //返回键值等于k的元素个数
void swap();
- 运用例子:
#include <map>
#include <string>
#include <iostream>
// 基本操作与set类型,牢记map中所有元素都是pair
// 对于自定义类,初学者会觉得比较函数如何构造很麻烦,这个可以参照前面的书写示例
// 但若设置键值为int或char类型,无须构造比较函数
struct student{
char* name;
int age;
char* city;
char* phone;
};
int main()
{
using namespace std;
student s[]={
{"童进",23,"武汉","XXX"},
{"老大",23,"武汉","XXX"},
{"饺子",23,"武汉","XXX"},
{"王老虎",23,"武汉","XXX"},
{"周润发",23,"武汉","XXX"},
{"周星星",23,"武汉","XXX"}
};
pair<int,student> p1(4,s[0]);
pair<int,student> p2(2,s[1]);
pair<int,student> p3(3,s[2]);
pair<int,student> p4(4,s[3]); //键值key与p1相同
pair<int,student> p5(5,s[4]);
pair<int,student> p6(6,s[5]);
multimap<int,student> a;
a.insert(p1);
a.insert(p2);
a.insert(p3);
a.insert(p4);
a.insert(p5);
a.insert(p6);
typedef multimap<int,student>::iterator int_multimap;
pair<int_multimap,int_multimap> p = a.equal_range(4);
int_multimap i = a.find(4);
cout<<"班上key值为"<< i->first<<"的学生有:"<<a.count(4)<<"名,"<<" 他们是:"<<endl;
for(int_multimap k = p.first; k != p.second; k++)
{
cout<<k->second.name<<endl;
}
cout<<"删除重复键值的同学"<<endl;
a.erase(i);
cout<<"现在班上总人数为:"<<a.size()<<". 人员如下:"<<endl;
for(multimap<int,student>::iterator j=a.begin(); j != a.end(); j++)
{
cout<<"The name: "<<j->second.name<<" "<<"age: "<<j->second.age<<" "
<<"city: "<<j->second.city<<" "<<"phone: "<<j->second.phone<<endl;
}
return 0;
}
四. hash_map
- hash_map的用法和map是一样的,提供了 insert,size,count等操作,并且里面的元素也是以pair类型来存贮的。
- 虽然对外部提供的函数和数据类型是一致的,但是其底层实现是完全不同的,map底层的数据结构是红黑树,hansh_map却是哈希表来实现的。
- 总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据量大小无关,属于常数级别;而map的查找速度是log(n)级别。hash还有hash函数的耗时。当有100w条记录的时候,map也只需要20次的比较,200w也只需要21次的比较!所以并不一定常数就比log(n) 小!
- hash_map对空间的要求要比map高很多,所以是以空间换时间的方法,而且,hash_map如果hash函数和hash因子选择不好的话,也许不会达到你要的效果。所以用map,还是hash_map,从3个方面来权衡: 查找速度、数据量、内存使用。
- 另外可以通过重写 hash_compair仿函数,更改里面关于桶数量的定义,如果取值合适,也可以得到更优的性能。而且如果你的数据是自定义的类型,必须要重写这个仿函数。可以模仿原来的写法,所有的成员函数,成员变量一个不能少!
- 另外再有两篇参考文章:
hash_map (STL/CLR)【微软STL文档】
[Z]C++ STL中哈希表 hash_map介绍
五. unordered_map
- hash_map并非标准STL中的,VS直接#include<unordered_map>会提醒你这个库可能变化比较大,建议你换用其他的。所以现在比较常用的是类似的unordered_map。
- hash_map内部是一个hash_table一般是由一个大vector,vector元素节点可挂接链表来解决冲突,来实现。
- unordered_map需要定义hash_value函数并且重载operator==。对于内置类型,如string,这些都不用操心。对于自定义的类型做key,就需要自己重载operator< 或者hash_value()了。
- 一个使用例子:
#include<iostream>
#include<unordered_map>
#include<string>
using namespace std;
/*
一个应用哈希表的例子:
查找一个字符串中第一个只出现一次的数
*/
int main() {
string in = "abcdefabcdf";
unordered_map<char,int> hashtable;
for (int i = 0; i < in.length(); i++) {
//key是每一个字符,value是出现次数
unordered_map<char, int>::iterator one;
one = hashtable.find(in[i]);
//如果没有,直接插入,并且标记为1
if (one == hashtable.end()) {
hashtable.insert(make_pair(in[i], 1));
}
//如果有,该位置加一
else {
one->second = one->second + 1;
}
}
//查找并输出
for (unordered_map<char, int>::iterator i = hashtable.begin(); i != hashtable.end(); i++) {
if (i->second == 1) {
cout << i->first << endl;
break;
}
}
getchar();
return 0;
}