C++ map, unordered_map

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记录;能遍历所有记录,能快速删除记录。

注意啦:

  1. STL是一个统一的整体,map的很多用法都和STL中其它的东西结合在一起,比如排序,这里默认用的是小于号,即less<>,如果要从大到小排序呢?
  2. 由于map中内部有序,由红黑树保证,因此很多函数执行的时间复杂度都是logN(2为底)的,如果用map函数可以实现的功能,而STL Algorithm也可以完成,建议用map自带函数,效率更高。
  3. 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->firstiterator->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(很详细)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值