C++ 进阶:unordered_map 与 unordered_set 超全详解(哈希容器实战)

C++ 进阶:unordered_map 与 unordered_set 超全详解(哈希容器实战)

在 C++ 标准库中,unordered_mapunordered_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

五、自定义类型放入哈希容器

默认不支持自定义类型,必须提供:

  1. 哈希函数
  2. 判等函数

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;

六、使用场景总结

  1. 追求极致查找速度 → 用 unordered_map/unordered_set
  2. 需要有序遍历 → 用 map/set
  3. 去重+快速判存 → unordered_set
  4. 键值对映射 → unordered_map
  5. 自定义类型 → 必须实现哈希与判等规则
相关推荐
leaves falling1 天前
C/C++ 的内存管理,函数栈帧详讲
java·c语言·c++
文静小土豆1 天前
Java 应用上 K8s 全指南:从部署到治理的生产级实践
java·开发语言·kubernetes
wuyoula1 天前
AI导航智能决策系统源码 附教程
c++·tcp/ip·源码
西西弗Sisyphus1 天前
Python 在终端里彩色打印
开发语言·python·print·彩色打印
浅念-1 天前
从LeetCode入门位运算:常见技巧与实战题目全解析
数据结构·数据库·c++·笔记·算法·leetcode·牛客
Rsun045511 天前
3、Java 工厂方法模式从入门到实战
java·开发语言·工厂方法模式
wjs20241 天前
C++ 基本的输入输出
开发语言
码云数智-园园1 天前
Python的GIL锁如何影响多线程性能?有哪些替代方案?
开发语言
咬_咬1 天前
go语言学习(map)
开发语言·学习·golang·map
古城小栈1 天前
rustup 命令工具,掌控 Rust 开发环境
开发语言·后端·rust