C++ 进阶:unordered_map 与 unordered_set 超全详解(哈希容器实战)
在 C++ 标准库中,unordered_map 和 unordered_set 是基于哈希表(哈希桶)实现的关联容器,以平均 O(1) 的增删查改效率成为高性能场景下的首选。本文从底层原理、核心用法、对比测试、自定义类型哈希等角度,带你彻底掌握哈希容器。
文章目录
- [C++ 进阶:unordered_map 与 unordered_set 超全详解(哈希容器实战)](#C++ 进阶:unordered_map 与 unordered_set 超全详解(哈希容器实战))
-
- 一、哈希容器核心认知
-
- [1.1 底层结构](#1.1 底层结构)
- [1.2 核心特性](#1.2 核心特性)
- [1.3 模板参数](#1.3 模板参数)
- [二、unordered_set 完整用法](#二、unordered_set 完整用法)
-
- [2.1 头文件与初始化](#2.1 头文件与初始化)
- [2.2 插入元素](#2.2 插入元素)
- [2.3 查找与判存](#2.3 查找与判存)
- [2.4 删除元素](#2.4 删除元素)
- [2.5 遍历与容量](#2.5 遍历与容量)
- [2.6 unordered_multiset(允许重复)](#2.6 unordered_multiset(允许重复))
- [三、unordered_map 完整用法](#三、unordered_map 完整用法)
-
- [3.1 初始化](#3.1 初始化)
- [3.2 插入数据](#3.2 插入数据)
- [3.3 查找与访问](#3.3 查找与访问)
- [3.4 删除与遍历](#3.4 删除与遍历)
- [3.5 unordered_multimap(key可重复)](#3.5 unordered_multimap(key可重复))
- [四、性能对比:unordered vs 红黑树](#四、性能对比:unordered vs 红黑树)
-
- [4.1 测试代码(百万级数据)](#4.1 测试代码(百万级数据))
- [4.2 测试结论](#4.2 测试结论)
- 五、自定义类型放入哈希容器
-
- [5.1 示例:自定义结构体](#5.1 示例:自定义结构体)
- [5.2 仿函数写法(推荐)](#5.2 仿函数写法(推荐))
- 六、使用场景总结
一、哈希容器核心认知
1.1 底层结构
unordered_set/unordered_map:哈希桶实现set/map:红黑树实现
1.2 核心特性
| 特性 | unordered系列 | set/map系列 |
|---|---|---|
| 遍历顺序 | 无序 | 有序(中序) |
| 时间复杂度 | 平均 O(1) | 稳定 O(logN) |
| 迭代器类型 | 单向迭代器 | 双向迭代器 |
| Key要求 | 可哈希、可判等 | 可小于比较 |
| 去重 | 是(multi版本允许重复) | 是(multi版本允许重复) |
1.3 模板参数
cpp
// unordered_set 模板
template <
class Key,
class Hash = hash<Key>, // 哈希函数
class Pred = equal_to<Key>, // 判等规则
class Alloc = allocator<Key> // 空间配置器
> class unordered_set;
// unordered_map 模板
template <
class Key,
class T,
class Hash = hash<Key>,
class Pred = equal_to<Key>,
class Alloc = allocator<pair<const Key, T>>
> class unordered_map;
二、unordered_set 完整用法
unordered_set 存储唯一 Key,只存键、不存值。
2.1 头文件与初始化
cpp
#include <iostream>
#include <unordered_set>
#include <string>
using namespace std;
int main() {
// 1. 默认初始化
unordered_set<int> us1;
// 2. 列表初始化
unordered_set<int> us2 = {1, 2, 3, 3, 4}; // 自动去重
// 3. 迭代器初始化
int arr[] = {10, 20, 30};
unordered_set<int> us3(arr, arr + 3);
return 0;
}
2.2 插入元素
cpp
// 单个插入
us1.insert(5);
// 批量插入
us1.insert({6, 7, 8});
// 返回值:pair<迭代器, 是否插入成功>
auto ret = us1.insert(5);
if (!ret.second) {
cout << "5 已存在" << endl;
}
2.3 查找与判存
cpp
// find:存在返回迭代器,不存在返回 end()
auto it = us1.find(5);
if (it != us1.end()) {
cout << "找到:" << *it << endl;
}
// count:快速判存(返回 0 或 1)
if (us1.count(6)) {
cout << "6 存在" << endl;
}
2.4 删除元素
cpp
// 按值删除
us1.erase(5);
// 按迭代器删除
it = us1.find(6);
if (it != us1.end()) {
us1.erase(it);
}
// 清空
us1.clear();
2.5 遍历与容量
cpp
// 范围for遍历(无序)
for (auto e : us2) {
cout << e << " ";
}
cout << endl;
// 迭代器遍历
for (auto it = us2.begin(); it != us2.end(); ++it) {
cout << *it << " ";
}
// 容量接口
cout << "size: " << us2.size() << endl;
cout << "empty: " << us2.empty() << endl;
2.6 unordered_multiset(允许重复)
cpp
unordered_multiset<int> ums = {1,1,2,2,3};
cout << ums.count(1) << endl; // 输出 2
三、unordered_map 完整用法
unordered_map 存储键值对 <key, value>,key 唯一。
3.1 初始化
cpp
#include <unordered_map>
// 1. 默认初始化
unordered_map<int, string> um1;
// 2. 列表初始化
unordered_map<int, string> um2 = {
{1, "apple"},
{2, "banana"},
{3, "orange"}
};
3.2 插入数据
cpp
// 1. insert 插入 pair
um1.insert(make_pair(1, "apple"));
um1.insert({2, "banana"});
// 2. operator[] 插入/修改(最常用)
um1[3] = "orange";
um1[3] = "grape"; // 修改
3.3 查找与访问
cpp
// find 查找
auto mit = um2.find(2);
if (mit != um2.end()) {
cout << mit->first << ": " << mit->second << endl;
}
// at() 访问(key不存在抛异常)
cout << um2.at(1) << endl;
// operator[] 访问(key不存在会插入默认值)
cout << um2[4] << endl; // 插入 {4, ""}
3.4 删除与遍历
cpp
// 按key删除
um2.erase(2);
// 范围for遍历
for (auto& kv : um2) {
cout << kv.first << " " << kv.second << endl;
}
3.5 unordered_multimap(key可重复)
cpp
unordered_multimap<int, string> umm = {
{1, "a"}, {1, "b"}, {2, "c"}
};
// 遍历同一key的所有值
auto range = umm.equal_range(1);
for (auto it = range.first; it != range.second; ++it) {
cout << it->second << " ";
}
四、性能对比:unordered vs 红黑树
4.1 测试代码(百万级数据)
cpp
#include <iostream>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <ctime>
using namespace std;
const size_t N = 1000000;
void test_set_unordered_set() {
vector<int> v;
v.reserve(N);
srand((unsigned int)time(nullptr));
for (size_t i = 0; i < N; ++i) {
v.push_back(rand() + i);
}
// set 插入
set<int> s;
size_t begin1 = clock();
for (auto e : v) s.insert(e);
size_t end1 = clock();
cout << "set insert: " << end1 - begin1 << "ms" << endl;
// unordered_set 插入
unordered_set<int> us;
us.reserve(N); // 预分配,避免扩容
size_t begin2 = clock();
for (auto e : v) us.insert(e);
size_t end2 = clock();
cout << "unordered_set insert: " << end2 - begin2 << "ms" << endl;
// 查找测试
int cnt1 = 0;
size_t begin3 = clock();
for (auto e : v) if (s.find(e) != s.end()) cnt1++;
size_t end3 = clock();
cout << "set find: " << end3 - begin3 << "ms" << endl;
int cnt2 = 0;
size_t begin4 = clock();
for (auto e : v) if (us.find(e) != us.end()) cnt2++;
size_t end4 = clock();
cout << "unordered_set find: " << end4 - begin4 << "ms" << endl;
}
int main() {
test_set_unordered_set();
return 0;
}
4.2 测试结论
- 插入:unordered 远快于 set/map
- 查找:unordered 平均 O(1),速度碾压
- 有序需求:必须用 set/map
五、自定义类型放入哈希容器
默认不支持自定义类型,必须提供:
- 哈希函数
- 判等函数
5.1 示例:自定义结构体
cpp
struct Person {
string name;
int age;
// 判等
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};
// 特化哈希函数
namespace std {
template<> struct hash<Person> {
size_t operator()(const Person& p) const {
return hash<string>()(p.name) ^ hash<int>()(p.age);
}
};
}
// 使用
int main() {
unordered_set<Person> sp;
sp.insert({"Alice", 20});
return 0;
}
5.2 仿函数写法(推荐)
cpp
// 哈希仿函数
struct HashPerson {
size_t operator()(const Person& p) const {
return hash<string>()(p.name) ^ hash<int>()(p.age);
}
};
// 判等仿函数
struct EqualPerson {
bool operator()(const Person& p1, const Person& p2) const {
return p1.name == p2.name && p1.age == p2.age;
}
};
// 声明时传入
unordered_set<Person, HashPerson, EqualPerson> sp;
六、使用场景总结
- 追求极致查找速度 → 用 unordered_map/unordered_set
- 需要有序遍历 → 用 map/set
- 去重+快速判存 → unordered_set
- 键值对映射 → unordered_map
- 自定义类型 → 必须实现哈希与判等规则