🚀 新一代关联容器:unordered家族
如果你已经熟悉了map和set,那么unordered_map和unordered_set就是它们的快速版。简单来说:
set→unordered_set:有序变无序,速度更快map→unordered_map:有序变无序,速度更快
底层实现从红黑树变成了哈希表,时间复杂度从O(logN)降到了平均O(1)!
🔑 核心差异一览表
| 特性 | map/set(红黑树) | unordered_map/set(哈希表) |
|---|---|---|
| 底层结构 | 红黑树(平衡二叉搜索树) | 哈希表(数组+链表/红黑树) |
| 时间复杂度 | O(logN) | 平均O(1),最坏O(N) |
| 有序性 | 按键值有序排列 | 无序(名字里就告诉你了!) |
| 迭代器类型 | 双向迭代器(可++和--) | 前向迭代器(只能++) |
| Key要求 | 支持<比较 | 1. 支持==比较 2. 能转为整型(用于哈希) |
💻 基本使用:跟map/set几乎一样
好消息是,unordered_系列的使用接口跟map/set几乎一模一样!
cpp
#include <unordered_set>
#include <unordered_map>
#include <iostream>
int main() {
// unordered_set使用
std::unordered_set<int> us = {5, 2, 7, 2, 8, 5, 9};
for (int num : us) {
std::cout << num << " ";
// 输出可能是: 9 8 7 2 5(无序!)
}
// unordered_map使用
std::unordered_map<std::string, int> scores;
scores["小明"] = 90;
scores["小红"] = 85;
scores["小刚"] = 88;
// 同样支持operator[]
std::cout << "小明的分数: " << scores["小明"] << std::endl;
return 0;
}
⚡ 性能对比:谁更快?
理论归理论,实际跑一跑:
cpp
#include <unordered_set>
#include <set>
#include <vector>
#include <ctime>
#include <iostream>
int main() {
const int N = 1000000; // 100万数据
std::vector<int> v;
// 生成随机数据
for (int i = 0; i < N; ++i) {
v.push_back(rand() + i);
}
std::set<int> s;
std::unordered_set<int> us;
// 测试插入性能
clock_t begin1 = clock();
for (auto e : v) s.insert(e);
clock_t end1 = clock();
std::cout << "set插入耗时: " << end1 - begin1 << "ms\n";
clock_t begin2 = clock();
us.reserve(N); // 预分配空间,提升性能
for (auto e : v) us.insert(e);
clock_t end2 = clock();
std::cout << "unordered_set插入耗时: " << end2 - begin2 << "ms\n";
// 测试查找性能
// ...(类似代码)
return 0;
}
典型结果:
- 插入:
unordered_set比set快2-10倍 - 查找:
unordered_set比set快10-100倍 - 内存:
unordered_set通常占用更多(哈希表有数组开销)
🎯 什么时候用unordered系列?
优先使用unordered_系列:
-
只需要快速查找,不需要有序
cpp// 检查用户名是否存在(不关心顺序) std::unordered_set<std::string> users = {"Alice", "Bob", "Charlie"}; if (users.find(input_name) != users.end()) { // 用户存在 } -
统计频率/计数
cpp// 统计单词频率(最快的方法!) std::unordered_map<std::string, int> wordCount; for (const auto& word : words) { wordCount[word]++; // 平均O(1)! } -
缓存系统
cpp// LRU缓存实现(简化版) std::unordered_map<int, std::string> cache; // 快速检查缓存命中
使用map/set系列:
-
需要有序遍历
cpp// 按分数从高到低显示 std::map<int, std::string, std::greater<int>> scoreBoard; -
需要范围查询
cpp// 查找分数在80-90之间的学生 auto low = scores.lower_bound(80); auto high = scores.upper_bound(90); -
Key不支持哈希
cppstruct Point { int x, y; }; // Point不能直接用作unordered_set的Key // 但可以作为set的Key(需要实现operator<)
🔧 自定义类型作为Key
如果你想用自定义类型作为unordered_容器的Key,需要做两件事:
cpp
struct Person {
std::string name;
int age;
// 1. 实现相等比较
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};
// 2. 实现哈希函数
struct PersonHash {
std::size_t operator()(const Person& p) const {
// 组合name和age的哈希值
return std::hash<std::string>{}(p.name) ^
(std::hash<int>{}(p.age) << 1);
}
};
int main() {
std::unordered_set<Person, PersonHash> people;
people.insert({"小明", 18});
people.insert({"小红", 20});
return 0;
}
⚠️ 注意事项
1. 迭代器失效
cpp
std::unordered_set<int> s = {1, 2, 3, 4, 5};
auto it = s.find(3);
s.erase(2); // 这里可能导致it失效(取决于实现)
// 安全做法:先保存值,再删除
int value = *it;
s.erase(it); // 用迭代器删除是安全的
2. 内存使用
cpp
std::unordered_set<int> us;
us.reserve(1000); // 预分配空间,避免多次扩容
// 查看当前状态
std::cout << "桶数量: " << us.bucket_count() << std::endl;
std::cout << "负载因子: " << us.load_factor() << std::endl;
3. 最坏情况
虽然平均O(1),但最坏情况可能退化为O(N):
- 所有元素哈希到同一个桶
- 哈希函数设计不好
🔄 与multiset/multimap的关系
也有对应的unordered_multiset和unordered_multimap,允许重复Key:
cpp
std::unordered_multiset<int> ums = {1, 2, 2, 3, 3, 3};
std::cout << ums.count(2); // 输出: 2
// unordered_multimap不支持operator[]
std::unordered_multimap<std::string, int> umm;
umm.insert({"小明", 90});
umm.insert({"小明", 95}); // 允许重复key
🎓 实际应用场景
场景1:两数之和(LeetCode 1)
cpp
std::vector<int> twoSum(std::vector<int>& nums, int target) {
std::unordered_map<int, int> numToIndex; // 值->索引
for (int i = 0; i < nums.size(); i++) {
int complement = target - nums[i];
if (numToIndex.find(complement) != numToIndex.end()) {
return {numToIndex[complement], i};
}
numToIndex[nums[i]] = i;
}
return {};
}
场景2:查找重复元素
cpp
bool hasDuplicate(std::vector<int>& nums) {
std::unordered_set<int> seen;
for (int num : nums) {
if (seen.find(num) != seen.end()) {
return true;
}
seen.insert(num);
}
return false;
}
场景3:分组异位词(LeetCode 49)
cpp
std::vector<std::vector<std::string>> groupAnagrams(
std::vector<std::string>& strs) {
std::unordered_map<std::string, std::vector<std::string>> groups;
for (const auto& str : strs) {
std::string key = str;
std::sort(key.begin(), key.end()); // 排序作为key
groups[key].push_back(str);
}
std::vector<std::vector<std::string>> result;
for (const auto& pair : groups) {
result.push_back(pair.second);
}
return result;
}
💡 选择指南
快速决策流程:
需要关联容器吗?
↓
需要有序吗?
↓ 是 → 用map/set(红黑树)
↓ 否
需要最快查找吗?
↓ 是 → 用unordered_map/unordered_set(哈希表)
↓ 否 → 都可以,根据具体情况选择
经验法则:
- 默认选择 :大多数情况下用
unordered_系列,更快 - 需要顺序 :用
map/set - 内存敏感 :考虑
map/set(哈希表有额外开销) - 简单类型 :用
unordered_系列 - 复杂类型 :如果能实现好的哈希函数,用
unordered_;否则用map/set
🎉 总结
unordered_map和unordered_set是C++11引入的重要改进:
- 优点:查找极快(平均O(1)),使用简单
- 缺点:无序,内存占用可能更多
记住这个简单规则:
- 要快 →
unordered_系列 - 要有序 →
map/set系列
两者接口几乎一样,切换成本很低。在实际项目中,根据需求灵活选择,就能写出既高效又优雅的代码!

最后在这里祝大家在2026新的一年里平安喜乐,万事顺遂,勇往直前!
2026年,愿我们:
写更优雅的代码
解更复杂的问题
建更强大的系统
过更丰富的人生
Happy New Year, Happy Coding! 🎆