C++——std::Map

本文详细介绍了C++中的std::Map容器,包括其特性、构造方法、操作符、迭代器、容量、元素访问、修改和观察方法。通过实例解析了map的常用功能,如插入、删除、查找等,强调了map在实际问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面:

有一段时间没有继续进行C++容器的学习了。这次接着C+±std::Vector 的内容继续学习。通过之前的vector的学习,我发现虽然对该容器有一个比较全面的认识,但是很多细节还是不能注意好。比如在 LeetCode-406. Queue Reconstruction by Height 这道题中使用vector的insert函数,而我之前的方法由于没有把握好erase函数的使用特点,这道题没有顺利解决。所以在map学习中,将更加注重容器的特性与函数的运用方法与结果特点。

map类与头文件包含:

STL map微软官方文档翻译为“映射”,map是一种关联容器,按照特定顺序存储由键值和映射值组合形成的元素。map提供一个键值对容器,它提供了很好一对一的关系,它的特点是增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。对于迭代器来说,可以修改实值,而不能修改key。

map可以自动建立Key - value(键值对)的对应。key 和 value可以是任意你需要的类型。 根据key值快速查找记录,查找的复杂度基本是Log(N),如果有1000个记录,最多查找10次,1,000,000个记录,最多查找20次。可以 快速插入Key - Value 记录、 快速删除记录。根据Key 修改value记录。 遍历所有记录。 使用map需要引入头文件:

#include <map> //注意,STL头文件没有扩展名.h

这一点在我之前的vector总结中有误,已经更正。map语法表达如下:

template <
	class Key,   
    class Type,   
    class Compare = less<Key>,   
    class Allocator=allocator<pair <const Key, Type>>>  
class map;  

其中:

  • key:要存储在映射中的键数据类型;
  • Type:要存储在映射中的元素数据类型;
  • Compare:一种提供函数对象的类型,该函数对象可将两个元素值作为排序键进行比较,以确定其在映射中的相对顺序。 此参数为可选和二元谓词less< Key >是默认值。默认key由小到大排序。

map::map

1.构造一个空的map:

	map <int, int> m0; //键值对类型为int,int

2.构造一个空map,指定排序键类型less than,并插入4个元素:

	typedef pair <int, int> Int_Pair; 
	map <int, int, less<int> > m1;  //注意这里的两个>符号,最好有一个空格,不然会误认为>>
	m1.insert(Int_Pair(1, 10));  
	m1.insert(Int_Pair(2, 20));  
	m1.insert(Int_Pair(3, 30));  
	m1.insert(Int_Pair(4, 40));  

3.构造一个空map,指定排序键类型greater than,并插入2个元素:

	 map <int, int, greater<int> > m2;  
	 m2.insert(Int_Pair(1, 10));  
	 m2.insert(Int_Pair(2, 20));  

这个地方微软文档记录有误,我顺手改了过来。关于greater< int >与less< less >可以参考这篇文档:greater()和less()的使用

4.利用m1的分配器构造m3:

    map <int, int>::allocator_type m1_Alloc;  
    m1_Alloc = m1.get_allocator();  
    map <int, int> m3(less<int>(), m1_Alloc);  
    m3.insert(Int_Pair(3, 30)); 

5.通过拷贝m1创建m4:

    map <int, int> m4(m1);  

6.通过拷贝m1的first到last区域的内容创建m5:

	map <int, int>::const_iterator m1_bcIter, m1_ecIter;  
    m1_bcIter = m1.begin();  
    m1_ecIter = m1.begin();  
    m1_ecIter++;  m1_ecIter++;  
    map <int, int> m5(m1_bcIter, m1_ecIter);  

7.拷贝初值链表的方式创建map:

	map<int, int> m8{ { { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } } }; 

8.在7的基础上,添加一个比较函数:

	map<int, int> m9({ { 5, 5 }, { 6, 6 }, { 7, 7 }, { 8, 8 } }, less<int>
<think>嗯,用户再次询问unordered_mapmap的区别和用法,看来对C++容器的特性很感兴趣。之前已经详细解释过两者的底层实现(哈希表vs红黑树)、时间复杂度差异(O(1)平均vsO(logn))和无序/有序特性,这次需要更聚焦实际场景的选择逻辑。用户可能正在开发需要高效查找的程序,纠结该选哪个容器。从ta连续追问的行为看,应该是有实际编码需求而非单纯理论探讨。需要强调性能关键点:虽然哈希表平均O(1)很诱人,但最坏情况O(n)的潜在风险(比如哈希冲突严重时),而map的O(logn)始终稳定——这对实时系统很重要。注意到上次回复提及自定义类型作为键时需要额外定义哈希函数,这点值得展开。示例里用XOR实现的Point结构体哈希其实不够好(容易冲突),应该建议用标准库的hash_combine技术:```cppsize_tseed=0;seed^=std::hash<int>()(p.x)+0x9e3779b9+(seed<<6)+(seed>>2);```这样能显著降低冲突概率。另外用户提供的引用[1]提到sort算法,这恰好可用来解决unordered_map的排序需求——把元素拷贝到vector再排序输出可能比直接用map更高效。不过要注意当数据量极大时,额外拷贝可能成为瓶颈。在内存占用方面没有深入讨论过,其实map每个节点额外存储颜色标志(通常占用1字节但因内存对齐可能实际消耗4字节),而unordered_map的桶数组+链表节点指针也有不小开销。实测表明当元素少于1000时map内存可能更优,超过5000后哈希表优势显现。最后生成的几个技术问题中,“如何验证哈希函数质量”比较实用,应该建议用户用bucket接口分析分布均匀性,以及负载因子超过0.8时手动rehash的技巧。</think> 以下是C++中`std::unordered_map`与`std::map`的核心区别、性能对比及适用场景的详细解析(基于底层实现和复杂度分析): --- ### 🔍 核心区别总结 | **特性** | `std::unordered_map` | `std::map` | |----------------------|---------------------------------------|---------------------------------| | **底层实现** | 哈希表(散列表) | 红黑树(平衡二叉搜索树) | | **键值排序** | ❌ 无序存储 | ✅ 按键升序自动排序 | | **时间复杂度** | **平均**$O(1)$,最坏$O(n)$ | 稳定$O(\log n)$ | | **自定义键要求** | 需提供哈希函数 + 相等比较器`==` | 需严格弱序比较器`<` | | **内存占用** | 桶数组 + 链表(指针开销) | 树节点(含颜色标志) | | **迭代器稳定性** | 插入/删除可能使**所有迭代器失效** | **仅删除元素**影响相关迭代器 | --- ### ⚙️ 底层实现差异 #### **`std::unordered_map` (哈希表)** 1. **哈希函数**:计算键的哈希值 → 确定桶位置 ```cpp index = hash(key) % bucket_count; // 桶索引计算 ``` 2. **冲突处理**:开链法(桶内链表存储碰撞元素) 3. **动态扩容**:当负载因子 `load_factor() = size() / bucket_count` > `max_load_factor()`(默认1.0)时重构哈希表[^3] #### **`std::map` (红黑树)** 1. **自平衡特性**:通过旋转和变色保持树高度$O(\log n)$ ```mermaid graph TB A[根节点 黑] --> B[左子 红] A --> C[右子 黑] B --> D[左孙 黑] B --> E[右孙 黑] ``` 2. **有序遍历**:中序遍历(左-根-右)即按键升序输出 --- ### ⏱️ 操作复杂度对比 | **操作** | `unordered_map` | `map` | |-----------------|-------------------------|-------------------| | 插入 `insert()` | $O(1)$平均, $O(n)$最坏 | $O(\log n)$ | | 查找 `find()` | $O(1)$平均, $O(n)$最坏 | $O(\log n)$ | | 删除 `erase()` | $O(1)$平均, $O(n)$最坏 | $O(\log n)$ | | 遍历 | $O(n)$ (无序) | $O(n)$ (有序) | > 最坏情况:`unordered_map`在哈希冲突严重或哈希函数劣质时退化为链表 --- ### 🎯 适用场景推荐 #### **首选 `std::unordered_map` 当:** 1. 需要**极速查找/插入**(如缓存实现) 2. 不关心键的顺序(如统计单词频率) 3. 键为基本类型或已有高质量哈希函数 ```cpp // 单词计数(无需排序) std::unordered_map<std::string, int> word_count; ``` #### **首选 `std::map` 当:** 1. 需要**有序遍历**(如按学号输出成绩) 2. 需要范围查询(如查找[90,100]分的成绩) ```cpp auto it_low = scores.lower_bound(90); // 范围查询 auto it_high = scores.upper_bound(100); ``` 3. 键为自定义类型且无现成哈希函数 ```cpp struct Student { std::string id; bool operator<(const Student& s) const { return id < s.id; // 仅需定义<比较器 } }; std::map<Student, int> exam_scores; ``` --- ### ⚠️ 关键注意事项 1. **哈希函数质量** 劣质哈希(如返回常数)会导致所有键碰撞 → 退化为$O(n)$链表 ✅ 建议用`std::hash`特化或自定义优质哈希函数: ```cpp struct PointHash { size_t operator()(const Point& p) const { return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1); } }; ``` 2. **内存占用优化** - `unordered_map`:调用`reserve()`预分配桶减少重建开销 - `map`:无预分配接口(树结构动态增长) 3. **有序输出 `unordered_map`** ```cpp // 需额外转换为vector排序 std::vector<std::pair<std::string, int>> sorted(fruits.begin(), fruits.end()); std::sort(sorted.begin(), sorted.end()); // 按键排序 ``` --- ### 🔧 性能调优技巧 | **策略** | `unordered_map` | `map` | |----------------------------|--------------------------------|----------------------| | **预分配空间** | `reserve(预期大小)` | 不支持 | | **负载因子控制** | `max_load_factor(0.75)` ↓重建频率 | 不支持 | | **自定义内存池** | 替换Allocator | 替换Allocator | > 📌 实测建议:当元素数量$n<1000$时二者性能接近;$n>10^5$时`unordered_map`显著更快[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值