1. map
1.1 map简介
- map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据 处理能力,能在处理一对一数据时,在编程上提供快速通道。
- 容器map的底层实现是红黑树,表明其内部数据结构是一个map对应一颗红黑树(一 种非严格意义上的平衡二叉树),红黑树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的,由小到大排序。
- map是一种关联式容器,特点是增加、删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。
- 通过map的迭代器,可以修改实值value,而不能修改键key。
- 自动建立key-value的对应,key 和 value可以是任意类型。
- 根据key值能快速查找记录value,查找的复杂度基本是log(N)(2为底的对数),如果有1000个记录,最多查找10次,1,000,000个记录,最多查找20次。
- 根据key值能修改value记录;能遍历所有记录,能快速删除记录。
注意啦:
- STL是一个统一的整体,map的很多用法都和STL中其它的东西结合在一起,比如排序,这里默认用的是小于号,即less<>,如果要从大到小排序呢?
- 由于map中内部有序,由红黑树保证,因此很多函数执行的时间复杂度都是logN(2为底)的,如果用map函数可以实现的功能,而STL Algorithm也可以完成,建议用map自带函数,效率更高。
- map在空间上的特性:由于map的每个数据对应红黑树上的一个节点,这个节点在不保存数据时,占用16个字节(一个父节点指针,左右孩子指针,还有一个枚举值(标示红黑的,相当于平衡二叉树中的平衡因子)),这些地方 很费内存!!!
1.2 map语法(C++)
^_^ map基本函数汇总:
//C++ maps是一种关联式容器,包含“关键字/值”对
begin(); //返回指向map头部的迭代器
clear(); //删除所有元素
count(); //返回指定元素出现的次数
empty(); //如果map为空则返回true
end(); //返回指向map末尾的迭代器
equal_range(); //返回特殊条目的迭代器对
erase(); //删除一个元素
find(); //查找一个元素
get_allocator(); //返回map的配置器
insert(); //插入元素
key_comp(); //返回比较元素key的函数
lower_bound(); //返回键值>=给定元素的第一个位置
max_size(); //返回可以容纳的最大元素个数
rbegin(); //返回一个指向map尾部的逆向迭代器
rend(); //返回一个指向map头部的逆向迭代器
size(); //返回map中元素的个数
swap(); //交换两个map
upper_bound(); //返回键值>给定元素的第一个位置
value_comp(); //返回比较元素value的函数
(1)声明
#include <map> //使用map得包含map类所在的头文件, 注意,STL头文件没有扩展名.h
using namespace std;
std:map<int,string> m;//map对象是模板类,需要关键字和存储对象两个模板参数
map<int,string> m;//定义了一个用int作为索引,并拥有相关联的指向string的指针
typedef map<int,CString> UDT_MAP_INT_CSTRING;//为了使用方便,对模板类进行类型定义
UDT_MAP_INT_CSTRING enumMap;
(2)构造函数
- map共提供了6个构造函数,涉及到内存分配器。通常用如下方法构造一个map
map<int, string> m;//常用的 map的构造函数
(3)插入数据(3种插入数据的方法)
- 用insert函数插入pair数据。 可以在VC和GCC下编译通过,在VC下请加入这条语句 “ #pragma warning (disable:4786) ”,屏蔽4786警告 )。
- 用insert函数插入value_type数据。
- 用数组方式插入数据。
- 区别:pair和value_type在效果上是完成一样的,用insert函数插入数据,在数据的 插入上涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作(包括pair和value_type)是插入不了数据的,但是用数组方式就不同了,用数组方式可以覆盖以前该关键字对应的值。
map<int, string> m;
//用insert函数插入pair数据
m.insert(pair<int, string>(1, "Alice"));
m.insert(pair<int, string>(2, "Lily"));
//用insert函数插入value_type数据
m.insert(map<int, string>::value_type (3, "Lucy"));
m.insert(map<int, string>::value_type (4, "Maike"));
//用数组方式插入数据
m[5] = "Rose";
m[6] = "Jack";
//----------
//在map中访问元素的方式:
map<int,int> m;
m.insert(pair<int,int>(8,9));
cout<<m->first<<' '<<m->second<<'\n'; //用->
- 可以用pair来获得insert是否插入成功,通过pair的第二个变量来知道是否插入成功,它的第一个变量返回的是一个map的迭代器,如果插入成功的话insert_pair.second应该是true的,否则为false,如下:
pair<map<int, string>::iterator, bool> insert_pair;
insert_pair = m.insert(map<int, string>::value_type (1, "Alice"));
if(insert_pair.second == true)
cout<<"Insert Successfully"<<endl;
else
cout<<"Insert Failure"<<endl;
insert_pair = m.insert(pair<int, string>(2, "Lily"));
if(insert_pair.second == true)
cout<<"Insert Successfully"<<endl;
else
cout<<"Insert Failure"<<endl;
(4)计算map大小
int mSize = m.size();
(5)遍历map数据(3种方法)
- 应用前向迭代器
- 应用反相迭代器
- 用数组的形式
//前向迭代器
map<int, string>::iterator iter1;
for(iter1 = m.begin(); iter1 != m.end(); iter1++)
cout<<iter1->first<<' '<<iter1->second<<endl;
//反向迭代器
map<int, string>::reverse_iterator iter2;
for(iter2 = m.rbegin(); iter2 != m.rend(); iter2++)
cout<<iter2->first<<" "<<iter2->second<<endl;
//应用数组的形式
int mSize = m.size();
/*
此处应注意,应该是 for(int mindex = 1; mindex <= mSize; mindex++)
而不是 for(int mindex = 0; mindex < mSize; nindex++)
*/
for(int mindex = 1; mindex <= mSize; mindex++)
cout<<m[mindex]<<endl;
(6)查找并获取map中的元素(包括判定被查找的关键字key是否在map中存在)
- 1)count() :用 count() 函数来判定关键字是否出现,其缺点是无法定位数据出现位置,map的一对一的映射关系决定了count函数的返回值只有两个,0或1。存在查找的元素返回1。
- 2)find() :用 find() 函数来定位数据出现位置,它返回一个迭代器,当数据出现时,它返回数据所在位置的迭代器,如果map中没有要查找的数据,它返回的迭代器等于end()函数返回的迭代器。
- find() :查找map中是否包含某个关键字条目用find()方法,传入的参数是要查找的key,在这里需要提到的是begin()和end()两个成员,分别代表map对象中第一个条目,和最后一个条目,的下一个位置,这两个数据的类型是iterator。
- iterator->first 和 iterator->second:通过map对象的方法获取的iterator数据类型是一个std::pair对象,包括两个数据 iterator->first和 iterator->second分别代表关键字key和存储的数据value。
//前向迭代器
map<int, string>::iterator iter;
iter = m.find(1);
if(iter != m.end()) //1作为key在map中存在
cout<<"Find, the value is "<<iter->second<<endl;
else
cout<<"Do not Find"<<endl;
-
3)lower_bound() 和 upper_bound() , 用来判定数据是否出现
- lower_bound函数用法,这个函数用来返回要查找关键字的下界(是一个迭代器)
- upper_bound函数用法,这个函数用来返回要查找关键字的上界(是一个迭代器)
- 例如:map中已经插入了1,2,3,4的话,如果lower_bound(2)的话,返回的2,而upper-bound(2)的话,返回的就是3
- 4)Equal_range() 函数返回一个pair,pair里面第一个变量是Lower_bound返回的迭代器,pair里面第二个迭代器是Upper_bound返回的迭代器,如果这两个迭代器相等的话,则说明map中不出现这个关键字。
(7)从map中删除元素
- erase(): 移除map中某个条目(key, value)用erase(), erase函数有三个重载了的函数
- 该成员方法的定义如下:
- iterator erase(iterator it);//通过一个条目对象删除
- size_type erase(const Key&key);//通过关键字删除
- iterator erase(iterator first,iterator last)//删除一个范围
- clear(): clear()相当于m.erase(m.begin(), mend());
//用迭代器删除1
map<int, string>::iterator iter;
iter = m.find(1);
m.erase(iter);
//用关键字删除1 : 如果删除了会返回1,否则返回0
int n = m.erase(1);
//用迭代器,成片的删除 : 一下代码把整个map清空
m.erase(m.begin(), m.end());
//成片删除要注意: 删除区间是一个前闭后开的集合(STL的特性)
//一下子清空所有元素
m.clear();
(8)map中的swap()
- map中的swap不是一个容器中的元素交换,而是两个容器所有元素的交换。
(9)排序 · map中的sort问题
- 为了实现快速查找,map内部本身就是按序(从小到大)存储的(比如红黑树), 在我们插入<key, value>键值对时,就会按照key的大小顺序进行存储!!
- map中的元素是自动按key升序排序,所以不能对map用sort函数。
- 高深用法:排序问题。STL中默认是采用小于号来排序的,以上代码在排序上是不存在任何问题的,因为上面的关键字是int 型,它本身支持小于号运算,在一些特殊情况,比如关键字是一个结构体,涉及到排序就会出现问题,因为它没有小于号操作,insert等函数在编译的时候过 不去,有两个方法可以解决这个问题。
- 1)第一种:小于号重载。
- 2)第二种:仿函数的应用,这个时候结构体中没有直接的小于号重载。
(10)获取map的最后一个元素
注意加切记:
mymap.end();
mymap.rend();
这两个函数只是标记找没找到,不是返回最后一个元素!
//正确做法!!!
//输出map中的最后一个元素
map<int, string>::reverse_iterator iter = m.rbegin(); //返回最后一个元素
if (iter != mymap.rend())
{
std::cout << iter->first << " => " << iter->second << '\n';
}
//错误做法!!!
//如果这样直接用会导致崩溃 !!
map<int, string>::iterator iter2 = m.end(); //无法返回最后一个元素,因为end()是指向最后一个元素的下一个位置
std::cout << iter2->first << " => " << iter2->second << '\n';
//正确做法!!!
map<int, string>::iterator iter3 = m.begin(); //返回第一个元素
if (iter3 != mymap.end())
{
std::cout << iter3->first << " => " << iter3->second << '\n';
}
2. unordered_map
2.1 unordered_map简介
- C++ STL中,容器
unordered_map的底层实现是
哈希表。
这一规定从 C++ 11开始的,根据 C++ 11 标准的推荐,用unordered_map
代替hash_map
。 - 数据结构中哈希表的相关知识:哈希表(key,value)是根据 键key 直接访问 值value 的一种数据结构,即哈希表通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度,这个映射函数叫做散列函数。换句话说:unordered_map 是一种关联容器,用于存储由关键值 (Key Value,以下称为Key 值) 和映射值 (Mapped Value,以下称为映射值) 组成的元素,并且允许根据其 Key 值快速检索各个元素。
- 哈希表的一个重要问题是如何解决映射冲突的问题。常用的解决映射冲突的方法有两种:开放地址法 和 链地址法。
- 在 unordered_map 容器中,Key 值通常用来唯一标识元素,映射值是与该 Key 值关联内容的对象。Key 值与映射值的类型可能不同。
- 在 unordered_map 内部,元素没有按照其 Key 值与映射值的任何顺序进行排序 ,而是根据它们的 Hash 值组织成桶,允许它们通过其 Key 值直接快速访问单个元素(通常具有常数等级的平均时间复杂度)。
- unordered_map 实现了直接访问操作符 (operator[]),它允许使用 Key 值作为输入参数,直接访问映射值。
容器中的迭代器至少是前向迭代器。
2.2 unordered_map与map的区别
- map是有序的,unordered_map是无序的!
- STL中,
map
对应的数据结构是 红黑树。红黑树是一种近似于平衡的二叉查找树,里面的数据是有序的。在红黑树上做查找操作的时间复杂度为 O(logN)。 - 而STL中,
unordered_map
对应的数据结构是 哈希表。哈希表的特点是查找效率高,时间复杂度为常数级别 O(1), 但额外空间复杂度比map要高出许多。 - unordered_map 容器与 map 容器相比,通过 Key 值访问各个元素的速度更快,然而通过其元素子集进行范围迭代的效率通常较低。
- 使用场景:对于需要高效率查询的情况,使用
unordered_map
容器更合适。而如果对内存大小比较敏感或者要求存储的数据(key,vale)按照key的大小排序的话,则可以用map
容器,map容器默认按照key从小到大排序。
2.3 unordered_map基本语法
unordered_map
的用法和map
大同小异
#include <iostream>
#include <unordered_map>
#include <string>
int main(int argc, char **argv) {
std::unordered_map<int, std::string> map;
map.insert(std::make_pair(3, "C++"));
map.insert(std::make_pair(6, "Java"));
map.insert(std::make_pair(14, "Erlang"));
std::unordered_map<int, std::string>::iterator it;
if ((it = map.find(6)) != map.end()) {
std::cout << it->second << std::endl;
}
return 0;
}
2.3 unordered_map使用自定义类
- 要使用哈希表,必须要有对应的计算散列值的算法以及判断两个值(或对象)是否相等的方法。
- 在 Java 中,Object 类里有两个重要方法:
hashCode
和equals
方法。其中hashCode
方法便是为散列存储结构服务的,用来计算散列值;而equals
方法则是用来判断两对象是否等价。由于所有的类都继承自java.lang.Object
类,因此所有类相当于都拥有了这两个方法。 - 而在 C++ 中没有自动声明这类函数,STL 只为 C++ 常用类提供了散列函数,因此如果想在
unordered_map
中使用自定义的类,则必须为此类提供一个哈希函数和一个判断对象是否相等的函数(e.g. 重载==
运算符)。如:
using std::string;
using std::cin;
using std::cout;
using std::endl;
using std::unordered_map;
class Person {
public:
string phone;
string name;
string address;
explicit Person() {}
explicit Person(string name, string phone, string address): name(name), phone(phone), address(address) {}
// overload operator==
bool operator==(const Person& p) {
return this->phone == p.phone && this->name == p.name
&& this->address == p.address;
}
inline friend std::ostream& operator<<(std::ostream& os, Person& p) {
os << "[Person] -> (" << p.name << ", " << p.phone << ", "
<< p.address << ")";
return os;
}
};
// declare hash<Person>
namespace std {
template <>
struct hash<Person> {
std::size_t operator()(const Person& p) const {
using std::size_t;
using std::hash;
using std::string;
// Compute individual hash values for first,
// second and third and combine them using XOR
// and bit shifting:
return ((hash<string>()(p.phone)
^ (hash<string>()(p.name) << 1)) >> 1)
^ (hash<string>()(p.address) << 1);
}
};
}
unordered_map<string, Person> phoneMap;
void selectByPhone() {
string phone;
cout << "Input the phone number: "; cin >> phone;
unordered_map<string, Person>::iterator it;
int size = phoneMap.size();
for(int pc = 0; pc < size; pc++) {
if((it = phoneMap.find(phone)) != phoneMap.end()) {
cout << "Query result: " << it->second << endl;
return;
}
}
cout << "Query result : target_not_found" << endl;
}
参考:
https://www.cnblogs.com/fnlingnzb-learner/p/5833051.html
https://www.sczyh30.com/posts/C-C/cpp-stl-hashmap/
https://blog.csdn.net/ajianyingxiaoqinghan/article/details/78542932(很详细)