993 字
5 分钟

std::map 与 std::unordered_map 特性对比

2026-04-21
浏览量 加载中...

std::map 与 std::unordered_map 特性对比#

概述#

基于键值对(Key-Value)的关联容器,分别提供底层为红黑树的有序存储,与底层为哈希表的无序存储。

基本用法与底层原理#

头文件<map><unordered_map>

template< class Key, class T >
class map; // (C++98) 基于红黑树实现,按键排序
template< class Key, class T >
class unordered_map; // (C++11) 基于哈希表实现,无序
  • 数据打包单元:插入数据时,容器内的每个元素是一个 std::pair<const Key, T> 对象。std::pair (头文件 <utility>)负责把键与值组合为一个具有 first(只读的 Key)和 second(可修改的 Value)的整体。
  • 两个容器提供极为相似的 API:insert, erase, find, count, 以及 operator[]

进阶用法与多角度对比#

1. 排序与遍历行为#

std::map 默认按键(Key)进行升序排列,可以在定义时通过提供自定义比较仿函数来改变排序规则:

// 默认升序(小到大)
std::map<int, std::string> map_asc;
// 降序排列
std::map<int, std::string, std::greater<int>> map_desc;

std::unordered_map 的迭代器遍历顺序通常表现为哈希桶的散列顺序,这不仅无序,而且在重新哈希(扩容)后甚至会发生改变。

2. 性能选择指南#

  • 需要键保持有序(如范围查询、按顺序输出) \rightarrow 使用 std::map,查找时间复杂度为 O(log N)
  • 只要求快速查找,不关心顺序 \rightarrow 优先使用 std::unordered_map,平均查找时间复杂度为 O(1),最坏退化为 O(N)

示例#

#include <iostream>
#include <map>
#include <unordered_map>
#include <string>
int main() {
std::map<std::string, int> scores;
scores["Alice"] = 95; // 使用 [] 赋值(若不存在则插入)
scores.insert({"Bob", 87}); // 使用初始化列表插入
scores.insert(std::make_pair("Charlie", 92)); // make_pair 自动推导类型
// 查找时推荐使用 find
auto it = scores.find("Bob");
if (it != scores.end()) {
std::cout << it->first << " : " << it->second << std::endl;
}
// 遍历:每个元素均为 const pair<const Key, Value>&
for (const auto& entry : scores) {
std::cout << entry.first << " -> " << entry.second << std::endl;
}
}

算法用例:打破输入顺序的“排序陷阱”#

题目(如 P5740:最厉害的学生):记录多名学生的各项成绩并输出总分最高的一名。如果有多个总分相同的学生,要求输出输入顺序靠前的那位

分析:如果在代码中使用 std::map<string, vector<int>> 来保存数据:

  1. std::map 会自动根据学生姓名的字典序进行重排,导致遍历顺序不再是输入顺序
  2. 当调用 max_element 并在总分相同时,它会返回第一次遇到的最大值,但这个顺序是按姓名排好序后的顺序
  3. 如果总分相同的学生字典序靠前、但输入较晚,系统会错误地输出该学生。

正确解法:舍弃关联容器,使用结构体 std::vector<Student> 保留原始输入顺序。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
struct Student {
std::string name;
int total;
};
int main() {
std::vector<Student> stu = {{"senpai", 169}, {"lxl", 147}, {"fafa", 153}};
// vector 天然保持输入顺序,max_element 返回遇到的首个最大值
auto best = std::max_element(stu.begin(), stu.end(),
[](const Student& a, const Student& b) {
return a.total < b.total;
});
std::cout << best->name;
}

常见问题#

  1. operator[] 的默认构造陷阱 当键不存在时,使用 [] 访问不仅查不到结果,还会隐式地插入一个执行默认构造(例如 int0)的新元素。
std::map<std::string, int> m;
// 错误:意图检查是否存在 "apple",却导致无意插入了 ("apple", 0)
if (m["apple"] == 100) {}
// 正确:使用 count 或 find 查找而不引入副作用
if (m.find("apple") != m.end() && m["apple"] == 100) {}
  1. 遍历时修改键for (const auto& entry : my_map) 循环中,entry.first 的类型强制为 const Key,无法修改键名。

复杂度#

std::map 查找、插入、删除复杂度均为 O(log N)std::unordered_map 查找、插入、删除平均复杂度为 O(1),最坏(哈希冲突严重)退化为 O(N)

关联知识点#

  • std::make_pair:便捷的模板函数,无需指定模板参数即可生成 std::pair
  • std::multimap / std::unordered_multimap:允许存储重复键的版本。

赞助支持

如果这篇文章对你有帮助,欢迎赞助支持!

赞助
std::map 与 std::unordered_map 特性对比
https://whywood.cn/posts/2026-4-20/map/
作者
𣐩
发布于
2026-04-21
许可协议
CC BY-NC-SA 4.0
最后更新于 2026-04-21

评论区

目录