一、概述
1、是什么
map是一类关联式容器,关联的本质在于元素值与某个特定的键相关联。增删节点对迭代器影响很小,对于迭代器而言,不可修改键,只能修改其对应的值。map内部自建一棵红黑树,对内部元素有自动排序的功能。
红黑树:一种二叉查找树,此外在每个节点上增加一个存储位表示节点的颜色,可以是red,也可以是black。通过对一条从根到叶子节点的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长两倍,因此是接近平衡的。作为一个二叉查找树,满足二叉查找树的一般性质。
二叉查找树,即有序二叉树,有如下性质:
- 任意节点的左子树不空,则左子树上所有节点的值均小于它的跟节点的值
- 任意节点的右子树不空,则右子树上所有节点的值均大于它的跟节点的值
- 任意节点的左、右子树也分别是二叉查找树
- 没有键值相等的节点
二叉查找树高度为lgn,一般操作执行时间为O(logn),如果二叉查找树退化为一棵具有n个节点的线性链后,则这些操作最坏情况的运行时间为O(n)。而红黑树增加了着色和相关性质,是的红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(logn)。
红黑树性质:
- 每个节点要么是red,要么是black
- 跟节点是black
- 每个叶子结点都是black
- 如果一个节点是red,那么它的两个儿子都是black
- 对于任意节点而言,其到叶子结点的每条路径都包含相同数目的black节点
这5个性质保证了一棵有n个节点的红黑树始终保持logn的高度。
2、功能
首先,使用map需要包含头文件#include<map>。自动简历key-value的一一对应关系,key和value可以是任意类型,但是key的类型需要支持<操作符。由上述可知,查找的复杂度基本是logn,如果有1000个记录,则最多需要查询10次,如果有1000000个记录,最多需要查询20次。当然也有添加、删除、修改value、遍历等功能。
二、map的使用
1、插入
map<int, string> m_str;
pair<map<int, string>::iterator, bool> is_suc;
//map的插入有三种方式
//1、insert(pair<>)
is_suc = m_str.insert(pair<int, string>(2, "yuhy"));
//可以用is_suc判断是否插入成功
if(is_suc.second){
cout << "successful" << endl;
}else{
cout << "failed" << endl;
}
//2、insert (value_type)
m_str.insert(map<int, string>::value_type(5, "scott"));
//3、用数组方式插入数据
m_str[1] = "LiMing";
2、遍历
//遍历方式四种:
//1、for each
for(auto it : m_str){
cout << "key:" << it.first << ", value:" << it.second << endl;
}
cout << "-------" << endl;
//2、前向迭代器
for(auto it = m_str.begin(); it != m_str.end(); it++){
cout << "key:" << it->first << ", value:" << it->second << endl;
}
cout << "-------" << endl;
//3、反向迭代器
for(auto it = m_str.rbegin(); it != m_str.rend(); it++){
cout << "key:" << it->first << ", value:" << it->second << endl;
}
cout << "-------" << endl;
//4、数组遍历方式
for(int i=0; i<m_str.size(); i++){
cout << "value:" << m_str[i] << endl;
}
3、访问
//operator[]和at()
map<int, int> t_map;
t_map[2] = 2;
t_map[4] = 4;
t_map[6] = 6;
cout << "key=2, value=" << t_map.at(2) << endl;
for(auto it : t_map){
cout << "key:" << it.first << ",value:" << it.second << endl;
}
t_map.at(2) += 1;
cout << "key=8, value=" << t_map[8] << endl;
for(auto it : t_map){
cout << "key:" << it.first << ",value:" << it.second << endl;
}
operator[]和at()的区别:
使用map[key],如果key不存在
,则会自动为您添加一个键,并对值进行初始化。
使用map.at(key), 会检查key,如果key不存在则会跑出异常。
4、查找
//查找,两种
//1、count,查询关键字是否出现,无法判定其位置,返回值0或者1,1表示存在
cout << "5 是否出现 :" ;
if(m_str.count(5) == 1){
cout << "是";
}else{
cout << "否";
}
cout << endl;
//2、find,返回值是迭代器,如果不存在则返回end()
map<int, string>::iterator res_find = m_str.find(5);
if(res_find == m_str.end()){
cout << "yuhy 不存在" << endl;
}else{
cout << "yuhy key:" << res_find->first << ", value:" << res_find->second << endl;
}
5、删除
//删除
//1、erase(k),删除键为k的元素,并返回删除的个数
map<int, string>::size_type res_erase = m_str.erase(5);
cout << "num of erase:" << res_erase << endl;
//2、erase(it),删除迭代器it指向的元素
m_str.erase(m_str.find(1));
//3、erase(it_f, it_e),删除一段范围内的元素
m_str.erase(m_str.begin(), m_str.end());
6、排序
map默认key是从小到大排序。
map定义:
template<class Key,
class T,
class Compare = less<Key>,
class Allocator = allocator<pair<const Key, T>>
>
class map;
有四个参数,第一个是key,第二个是value,第三个参数提供了默认的less,less是STL中的一个函数对象,调用操作符的类,其对象常常称为函数对象,他们是行为类似函数的对象。表现出一个函数的特征,就是通过“对象名 + (参数列表)”的方式使用一个类,其实质是对operator()操作符的重载。
less的实现:
template <class T>
struct less : binary_function< T , T, bool>{
bool operator()(const T& x, const T& y) const{
return x < y;
}
};
里面仅仅是对()运算符的重载,调用的是T的<运算符。当然,与less相对的还有greater。
方式一:如果key是自定义对象,重写<操作符
class Student{
public:
std::string name_;
int age_;
Student(std::string name, int age):name_(name),age_(age){}
Student(){}
bool operator < (const Student &s) const {
std::cout << "operator : < " << std::endl;
if(name_ != s.name_){
return name_ < s.name_;
}else{
return age_ < s.age_;
}
}
};
map<Student, string> m_stu;
m_stu.insert(pair<Student, string>(Student("yyy", 20), "yyy"));
m_stu.insert(pair<Student, string>(Student("aaa", 20), "aaa"));
m_stu.insert(pair<Student, string>(Student("yyy", 18), "yyy"));
cout << "--- 排序 ---" << endl;
for(auto it : m_stu){
cout << "key-name:" << it.first.name_ << ", key-age:" << it.first.age_ << ", value:" << it.second << endl;
}
方式二、重载()
class Comp{
public:
bool operator () (const Student &left, const Student &right) const {
std::cout << "operator ()" << std::endl;
if(left.name_ != right.name_){
return left.name_ < right.name_;
}else{
return left.age_ < right.age_;
}
}
};
map<Student, string, Comp> m_stu;
m_stu.insert(pair<Student, string>(Student("yyy", 20), "yyy"));
m_stu.insert(pair<Student, string>(Student("aaa", 20), "aaa"));
m_stu.insert(pair<Student, string>(Student("yyy", 18), "yyy"));
cout << "--- 排序 ---" << endl;
for(auto it : m_stu){
cout << "key-name:" << it.first.name_ << ", key-age:" << it.first.age_ << ", value:" << it.second << endl;
}
方式三:
class Student{
public:
std::string name_;
int age_;
Student(std::string name, int age):name_(name),age_(age){}
Student(){}
std::string show(){
return "name:" + name_ + ", age:" + std::to_string(age_);
}
bool operator < (const Student &s) const {
std::cout << "operator : < " << std::endl;
if(name_ != s.name_){
return name_ < s.name_;
}else{
return age_ < s.age_;
}
}
};
bool comp (const Student &left, const Student &right){
cout << "comp method" << endl;
if(left.name_ != right.name_){
return left.name_ < right.name_;
}else{
return left.age_ < right.age_;
}
}
map<Student, string, decltype(comp)*> m_stu(comp);
m_stu.insert(pair<Student, string>(Student("yyy", 20), "yyy"));
m_stu.insert(pair<Student, string>(Student("aaa", 20), "aaa"));
m_stu.insert(pair<Student, string>(Student("yyy", 18), "yyy"));
cout << "--- 排序 ---" << endl;
for(auto it : m_stu){
cout << "key-name:" << it.first.name_ << ", key-age:" << it.first.age_ << ", value:" << it.second << endl;
}
方式四:对map中的value进行排序
首先想到的是STL中的sort算法,但是sort只能对序列容器进行排序,只能是线性的(如vector、list、deque)。map是个集合容器,存储的元素是pair,不是线性存储的,所以不能直接用sort。
但是我们可以将map中的元素放到序列容器中,再对序列容器中的元素进行排序。有一个必要条件,序列容器中的元素必须是可比较的,即实现了<操作的。
在使用sort的时候,传入比较函数,实现对pair中的value进行比较。
bool cmp_value(const pair<Student, string>& a, const pair<Student, string>& b) {
return b.second < a.second;
}
map<Student, string, decltype(comp)*> m_stu(comp);
m_stu.insert(pair<Student, string>(Student("yyy", 20), "yyy"));
m_stu.insert(pair<Student, string>(Student("aaa", 20), "aaa"));
m_stu.insert(pair<Student, string>(Student("yyy", 18), "yyy"));
cout << "--- 排序 ---" << endl;
for(auto it : m_stu){
cout << "key-name:" << it.first.name_ << ", key-age:" << it.first.age_ << ", value:" << it.second << endl;
}
vector<pair<Student, string>> vec(m_stu.begin(), m_stu.end());
sort(vec.begin(), vec.end(), cmp_value);
for(auto it : vec){
cout << "key-name:" << it.first.name_ << ", key-age:" << it.first.age_ << ", value:" << it.second << endl;
}