C++11STL容器新特性(map vs unordered_map,红黑树 vs 哈希表)
顺序容器:vector、deque、list关联容器(有序):map、set(基于红黑树)无序容器(C++11新增):unordered_map、unordered_set(基于哈希表)// 自定义类型:学生int id;// 1. 对于map:需要定义operator<用于排序/*map底层的红黑树需要通过比较键的大小来维持结构平衡和有序性。对于基础类型(如 int、string 等),编译器已
·
C++11STL容器新特性(map vs unordered_map,红黑树 vs 哈希表)
一、STL容器简介
STL容器大致分为三类:
- 顺序容器:vector、deque、list
- 关联容器(有序):map、set(基于红黑树)
- 无序容器(C++11新增):unordered_map、unordered_set(基于哈希表)
二、map与unordered_map
- map
- 内部实现:红黑树(平衡二叉搜索树)
- 元素自动按key有序排列:(插入时就按 key 的比较规则 “定位插入”,始终维持有序状态;如 int 的升序、string 的字典序)
- 查找、插入、删除时间复杂度:O(log n)
- 适合需要有序数据的场景
#include<map>
#include<iostream>
using namespace std;
int main(){
map<int, string> mp;//默认升序
//map<int, string, greater<int>> mp; // 按key降序排列
mp[3] = "C";
mp[1] = "A";
mp[2] = "B";
for( auto& p : mp){
cout<<p.first<<"->"<<p.second<<endl;
}
}
- unordered_map
- 内部实现:哈希表
- 元素无序存储
- 查找、插入、删除平均复杂度:O(1)(最坏O(n))
- 适合对顺序无要求、只关心性能的场景
#include<unordered_map>
#include<iostream>
using namespace std;
int main(){
unordered_map<int,string> ump;
ump[3] = "C";
ump[1] = "A";
ump[2] = "B";
for(auto& p : ump){
cout<<p.first<<"->"<<p.second<<endl;
}
}
三、map vs unordered_map对比
| 特性 | map | unordered_map |
|---|---|---|
| 底层实现 | 红黑树 | 哈希表 |
| 是否有序 | 有序(按key升序) | 无序 |
| 插入/查找/删除 | O(log n) | 平均O(1), 最坏 O(n) |
| 内存占用 | 较小 | 较大(要维护哈希桶) |
| 迭代器稳定性 | 稳定 | 扩容时可能失效 |
| 使用场景 | 需要有序遍历 | 高性能查找,不关心顺序 |
四、哈希表的原理(unordered_map)
- 哈希函数
- 把key转换成哈希值(整数)
- 默认用std::hash,可以自定义
- 哈希冲突
- 两个key可以映射到同一个桶
- 解决办法:链地址法(拉链),即每个桶一个链表
- 扩容
- 当负载因子(元素数量 / 桶数量)超过阈值时,哈希表会扩容,rehash.
- 扩容会导致迭代器失效
五、代码演示:性能对比
#include<iostream>
#include<map> //标准map容器
#include<unordered_map> //无序map容器
#include<chrono> //用于时间测量
using namespace std;
using namespace chrono; //// 简化时间相关函数的使用
int main(){
const int N = 1e6; // 定义常量N为100万,表示要插入的元素数量
map<int,int> mp;
unordered_map<int,int> ump;
//map插入
auto start = high_resolution_clock::now();
for(int i = 0; i< N; i++)mp[i] = i;
auto end = high_resolution_clock::now():
cout<<"map 插入时间:"<<duration_cast<milliseconds>(end-start).count()<<"ms\n";
}
// unordered_map 插入
start = high_resolution_clock::now();
for (int i = 0; i < N; i++) ump[i] = i;
end = high_resolution_clock::now();
cout << "unordered_map 插入时间: " << duration_cast<milliseconds>(end - start).count() << "ms\n";
六、注意事项(坑点)
- unordered_map 无序,不要依赖其顺序遍历
- unordered_map 迭代器在rehash后失效,而map的迭代器稳定。
- unordered_map 查找性能依赖于哈希函数,如果哈希函数不好,会退化到O(n).
- map总是有序,适合做范围查询(如找第一个大于等于某值的key)。
- 自定义类型作为key时,需要实现:
- (1)operator (用于map)
- (2)std::hash 特化 + operator == 用于unordered_map
6.5自定义类型作为key时的map vs unordered_map代码对比
#include <iostream>
#include <map>
#include <unordered_map>
#include <string>
// 自定义类型:学生
struct Student {
int id;
std::string name;
Student(int i, std::string n) : id(i), name(n) {}
};
// 1. 对于map:需要定义operator<用于排序
/*
map底层的红黑树需要通过比较键的大小来维持结构平衡和有序性。
对于基础类型(如 int、string 等),编译器已经内置了比较规则;
但对于自定义结构体,编译器不知道如何比较两个对象的大小,
因此必须通过重载<运算符来定义比较逻辑(通常是基于结构体的某个或某些成员),
这样map才能正常工作。
*/
bool operator<(const Student& a, const Student& b) {
// 这里按id排序
return a.id > b.id;
}
// 2. 对于unordered_map:需要特化std::hash和定义operator==
// 特化std::hash,提供哈希函数
/*
1、unordered_map 底层基于哈希表实现,需要通过哈希函数将键(key)转换为哈希值(一个整数),
以此确定元素在哈希表中的存储位置。对于自定义类型(如 Student),标准库没有默认的哈希函数,
因此需要通过特化 std::hash 来提供。先通过 hash<int>() 计算 id 的哈希值,再通过 hash<std::string>() 计算 name 的哈希值
最后通过 ^(异或)和 << 1(左移 1 位)将两个哈希值组合,生成最终的哈希值
2、为什么要组合多个成员?
这里同时使用 id 和 name 来计算哈希值,是为了减少哈希冲突的概率。
如果只使用 id,那么当两个学生 id 相同但 name 不同时,会产生相同的哈希值,增加冲突概率。
3、命名空间的特殊性:
必须将特化版本放在 std 命名空间中,这是 C++ 标准允许的特殊情况(通常不建议向 std 命名空间添加内容),目的是让标准库能够正确找到这个哈希函数。
*/
namespace std {
template<> struct hash<Student> {
size_t operator()(const Student& s) const {
// 简单的哈希实现:结合id和name的哈希值
return hash<int>()(s.id) ^ (hash<std::string>()(s.name) << 1);
}
};
}
// 定义operator==用于哈希碰撞时的比较
bool operator==(const Student& a, const Student& b) {
// 两个学生被视为相等当且仅当id和name都相同
return a.id == b.id && a.name == b.name;
}
int main() {
// 使用map存储Student
std::map<Student, int> studentScores;
studentScores[Student(1, "Alice")] = 90;
studentScores[Student(2, "Bob")] = 85;
std::cout << "map中的元素:\n";
for (const auto& pair : studentScores) {
std::cout << "ID: " << pair.first.id << ", Name: " << pair.first.name
<< ", Score: " << pair.second << "\n";
}
// 使用unordered_map存储Student
std::unordered_map<Student, int> studentAges;
studentAges[Student(1, "Alice")] = 20;
studentAges[Student(2, "Bob")] = 21;
std::cout << "\nunordered_map中的元素:\n";
for (const auto& pair : studentAges) {
std::cout << "ID: " << pair.first.id << ", Name: " << pair.first.name
<< ", Age: " << pair.second << "\n";
}
return 0;
}
小结
- map: 红黑树,有序,O(log n),内存友好,迭代器稳定
- unordered_map: 哈希表,无序,平均O(1),适合快速查找
-
- map 保持了迭代器稳定性,但查询效率略低。
-
- unordered_map 追求查询效率,牺牲了迭代器稳定性;
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)