unordermap用法
unordered_map
是 C++ 标准库中的关联容器,提供了基于哈希表的键值对存储结构。与 map
(基于红黑树实现)不同,unordered_map
提供的是平均常数时间复杂度的查找、插入和删除操作,但不保证元素的顺序。
以下是 unordered_map
的详细用法说明:
1. 头文件
要使用 unordered_map
,需要包含头文件:
1 |
2. 基本定义
unordered_map
的基本模板定义如下:
1 | std::unordered_map<KeyType, ValueType, Hash = std::hash<KeyType>, KeyEqual = std::equal_to<KeyType>, Allocator = std::allocator<std::pair<const KeyType, ValueType>>> |
常用模板参数:
- KeyType:键的类型,需要支持哈希运算和相等比较。
- ValueType:值的类型。
- Hash:哈希函数,默认为
std::hash<KeyType>
。 - KeyEqual:键相等的比较函数,默认为
std::equal_to<KeyType>
。 - Allocator:内存分配器,默认为
std::allocator
。
3. 常用操作
3.1 创建和初始化
1 |
|
3.2 插入元素
1 | // 方法1:使用下标操作符 |
3.3 访问元素
1 | // 使用下标操作符访问或插入 |
3.4 删除元素
1 | // 根据键删除 |
3.5 遍历元素
1 | for (const auto& pair : umap) { |
3.6 其他常用方法
1 | // 获取大小 |
4. 性能优化
4.1 预分配桶数
如果预先知道元素的大致数量,可以通过 reserve
预分配内存,以减少哈希表的重哈希开销:
1 | umap.reserve(100); // 预分配足够容纳100个元素的桶 |
4.2 自定义哈希函数
如果键的类型是自定义类型或需要特殊的哈希策略,可以自定义哈希函数。例如,自定义结构体作为键:
1 | struct Point { |
4.3 自定义键相等比较
如果需要自定义键的比较逻辑,可以提供自定义的 KeyEqual
函数对象:
1 | struct PointEqual { |
5. 与 map
的比较
- 底层实现:
unordered_map
基于哈希表,实现的操作平均时间复杂度为常数级别;map
基于红黑树,实现的查找、插入、删除操作时间复杂度为对数级别。 - 元素顺序:
unordered_map
不保证元素的顺序;map
按键的顺序(通常是升序)存储元素。 - 适用场景:当需要快速查找、插入和删除,且不关心元素顺序时,选择
unordered_map
;当需要有序存储或按顺序遍历时,选择map
。
6. 完整示例
以下是一个使用 unordered_map
的完整示例:
1 |
|
输出示例:
1 | Apple count: 5 |
手写unordermap
1. 哈希表的基本原理
哈希表是一种基于键值对的数据结构,通过哈希函数(Hash Function)将键映射到表中的一个索引位置,以实现快速的数据访问。哈希表的关键特性包括:
- 哈希函数:将键映射到表中一个特定的桶(Bucket)或槽(Slot)。
- 冲突解决:当不同的键通过哈希函数映射到同一个桶时,需要一种机制来处理这些冲突。常见的方法有链地址法(Separate Chaining)和开放地址法(Open Addressing)。
- 负载因子(Load Factor):表示表中已存储元素的数量与表大小之间的比率。高负载因子可能导致更多的冲突,需要通过扩容来维持性能。
在本实现中,我们将采用链地址法来处理哈希冲突,即每个桶存储一个链表(或其他动态数据结构)来存储具有相同哈希值的元素。
2. 数据结构设计
HashNode 结构
HashNode
用于存储键值对及相关的指针,以构建链表。每个 HashNode
包含键、值和指向下一个节点的指针。
1 |
|
MyHashMap 类定义
MyHashMap
是我们自定义的哈希表实现,支持基本的 Map
操作和迭代器功能。它使用一个向量(std::vector
)来存储桶,每个桶是一个链表,用于处理冲突。
1 | template <typename Key, typename T, typename Hash = std::hash<Key>> |
3. 基本操作实现
构造函数及析构函数
初始化哈希表,设置初始容量和负载因子。
1 | // 构造函数 |
插入(Insert)
向哈希表中插入键值对。如果键已存在,则更新其值。
1 | template <typename Key, typename T, typename Hash> |
查找(Find)
根据键查找对应的值,返回指向值的指针。如果未找到,则返回 nullptr
。
1 | template <typename Key, typename T, typename Hash> |
删除(Erase)
根据键删除对应的键值对,返回删除是否成功。
1 | template <typename Key, typename T, typename Hash> |
清空(Clear)
删除哈希表中的所有元素,释放内存。
1 | template <typename Key, typename T, typename Hash> |
动态扩容(Rehashing)
当负载因子超过阈值时,扩展哈希表容量并重新分配所有元素。
1 | template <typename Key, typename T, typename Hash> |
获取大小和状态
1 | template <typename Key, typename T, typename Hash> |
4. 迭代器的实现
为了支持迭代器操作,使 MyHashMap
能够像标准容器一样被遍历,我们需要实现一个内部的 Iterator
类。
Iterator 类定义
Iterator
类需要跟踪当前桶的索引和当前节点指针。它还需要能够找到下一个有效的节点。
1 | // Iterator 构造函数 |
迭代器操作
在 MyHashMap
类中实现 begin()
和 end()
函数来返回迭代器。
1 | // begin() 函数 |
5. 完整代码示例
以下是完整的 MyHashMap
实现,包括所有上述内容:
1 |
|
6. 使用示例
以下是一个使用 MyHashMap
的示例,展示如何插入、查找、删除以及使用迭代器遍历元素。
1 | int main() { |
预期输出
1 | Map contents: |
注意:由于哈希表的桶顺序依赖于哈希函数的实现,输出顺序可能与预期有所不同。
Hash()
在上述 MyHashMap
实现中是一个哈希函数对象。让我们详细解释一下它的含义以及它在代码中的作用。
Hash
是什么?
在 MyHashMap
的模板定义中,Hash
是一个模板参数,用于指定键类型 Key
的哈希函数。它有一个默认值 std::hash<Key>
,这意味着如果用户在实例化 MyHashMap
时没有提供自定义的哈希函数,std::hash<Key>
将被使用。
1 | template <typename Key, typename T, typename Hash = std::hash<Key>> |
默认情况下:std::hash<Key>
std::hash
是 C++ 标准库(STL)中提供的一个模板结构,用于为各种内置类型(如 int
, std::string
等)生成哈希值。std::hash<Key>
会根据 Key
的类型自动选择合适的哈希函数实现。
例如:
- 对于
int
类型,std::hash<int>
会生成一个简单的哈希值。 - 对于
std::string
类型,std::hash<std::string>
会基于字符串内容生成哈希值。
1 | std::hash<int> intHasher; |
自定义哈希函数
除了使用 std::hash
,用户还可以自定义哈希函数,以适应特定的需求或优化性能。例如,假设你有一个自定义的键类型 Point
:
1 | struct Point { |
你可以定义一个自定义的哈希函数 PointHasher
:
1 | struct PointHasher { |
然后,在实例化 MyHashMap
时使用自定义哈希函数:
1 | MyHashMap<Point, std::string, PointHasher> pointMap; |
Hash()
在代码中的作用
在 MyHashMap
的构造函数中,Hash()
用于实例化哈希函数对象,并将其赋值给成员变量 hash_func_
:
1 | // 构造函数 |
具体作用:
- 实例化哈希函数对象:
Hash()
会调用Hash
类型的默认构造函数,创建一个哈希函数对象。- 如果
Hash
是std::hash<Key>
,那么就创建一个std::hash<Key>
对象。
- 存储哈希函数对象:
- 生成的哈希函数对象被存储在成员变量
hash_func_
中,以便在哈希表的各种操作(如插入、查找、删除)中使用。
- 生成的哈希函数对象被存储在成员变量
- 支持不同的哈希函数:
- 由于
Hash
是一个模板参数,可以灵活地使用不同的哈希函数,无需修改MyHashMap
的内部实现。
- 由于
示例代码解释
以下是相关部分的简化示例,帮助理解 Hash
和 Hash()
的作用:
1 |
|
总结
Hash
是模板参数,用于指定键类型Key
的哈希函数。默认情况下,它使用 C++ 标准库中的std::hash<Key>
。Hash()
是一个默认构造函数调用,用于实例化哈希函数对象,并将其存储在hash_func_
成员变量中,以便在哈希表操作中使用。- 用户可以自定义哈希函数,通过提供自定义的哈希函数对象,实现对特定键类型的优化或满足特殊需求。