std::unorderd_map 简介
1. unorderd_map 简介
- 1. unorderd_map 简介
- 简介
- 1.1. 实现原理
- 1.2. 函数
- 1.3. 问题集
- 1.3.1. emplace、emplace_hint、insert 的区别
- 1.4. 参考链接
简介
- unordered_map 是 C++ 标准库中的一个容器,它定义在 <unordered_map> 头文件里。它借助哈希表来存储键值对,能快速查找、插入和删除元素
- 快速查找:平均情况下,查找、插入和删除操作的时间复杂度为 O ( 1 ) O(1) O(1)。不过在最坏情况下,例如哈希冲突严重时,时间复杂度会达到 O ( n ) O(n) O(n)。
- 无序存储:
unordered_map
不会按照键的顺序存储元素,元素的存储顺序由哈希函数决定。 - 键的唯一性:每个键在
unordered_map
中是唯一的。若尝试插入已存在的键,新的值会覆盖旧的值。
1.1. 实现原理
- 底层实现主要基于哈希表(Hash Table)
- 哈希函数
- unordered_map 使用哈希函数将键转换为一个无符号整数,这个整数将作为数组的索引。
- 对于基本数据类型(如 int、string 等),标准库已经提供了默认的哈希函数。
- 但对于自定义类型,需要用户自己定义哈希函数。
例如,对于自定义类型 MyKey,可以这样定义哈希函数:
#include <functional>struct MyKey {int a;double b;// 重载相等运算符,用于比较键是否相等bool operator==(const MyKey& other) const {return a == other.a && b == other.b;}
};// 自定义哈希函数
struct MyKeyHash {std::size_t operator()(const MyKey& k) const {// 使用 std::hash 对不同成员进行哈希,然后组合auto h1 = std::hash<int>{}(k.a);auto h2 = std::hash<double>{}(k.b);return h1 ^ (h2 << 1);}
};
- 哈希桶(Bucket)
- 哈希表通常由一个数组构成,数组中的每个元素称为一个哈希桶。
- 每个哈希桶可以存储一个或多个键值对,当多个键通过哈希函数映射到同一个桶时,就会发生哈希冲突。
- 处理哈希冲突
- 哈希冲突是指不同的键通过哈希函数映射到了同一个桶。
- unordered_map 通常使用链地址法(Separate Chaining)来处理哈希冲突。
- 在链地址法中,每个哈希桶实际上是一个链表(或其他容器,如红黑树),当发生哈希冲突时,新的键值对会被添加到对应的链表中。
以下是一个简单的示意图,哈希表数组:
+--------+--------+--------+
| Bucket0| Bucket1| Bucket2|
+--------+--------+--------+
| Node1 | Node3 | Node4 |
| Node2 | | |
+--------+--------+--------+
在这个示意图中,Bucket0 发生了哈希冲突,有两个键值对(Node1 和 Node2)被映射到了这个桶中,它们通过链表连接在一起。
- 插入操作
- 向 unordered_map 中插入一个键值对时,会执行以下步骤:
- 使用哈希函数计算键的哈希值。
- 根据哈希值找到对应的哈希桶。
- 检查该桶中是否已经存在相同的键,如果存在,则更新对应的值;如果不存在,则将新的键值对插入到桶中(通常是链表的头部)。
- 查找操作
- 当查找一个键对应的值时,会执行以下步骤:
- 使用哈希函数计算键的哈希值。
- 根据哈希值找到对应的哈希桶。
- 遍历该桶中的链表(或其他容器),查找是否存在与给定键相等的键。如果找到,则返回对应的值;如果未找到,则返回一个表示未找到的标记(如 end() 迭代器)。
- 删除操作
- 删除操作与查找操作类似,首先找到对应的哈希桶,然后遍历桶中的链表,找到要删除的键值对并将其从链表中移除。
- 负载因子(Load Factor)和扩容
- 负载因子是指哈希表中元素的数量与哈希桶数量的比值。
- 当负载因子超过某个阈值(通常是 1.0)时,为了减少哈希冲突,提高查找效率,unordered_map 会进行扩容操作。
- 扩容时,会创建一个更大的哈希表数组,然后将原有的键值对重新哈希到新的数组中。
总结:unordered_map 通过哈希表实现了快速的键值对查找、插入和删除操作。它使用哈希函数将键映射到哈希桶,通过链地址法处理哈希冲突,并在负载因子过高时进行扩容。这种实现方式使得 unordered_map 在平均情况下具有 O(1) 的时间复杂度。
1.2. 函数
-
成员方法
-
迭代器
begin
返回指向容器中第一个键值对的正向迭代器。end
返回指向容器中最后一个键值对之后位置的正向迭代器。cbegin
和 begin 功能相同,只不过在其基础上增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。cend
和 end 功能相同,只不过在其基础上,增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。
-
CRUD操作
-
operator[key]
该模板类中重载了 [] 运算符,只要给定某个键值对的键 key,就可以获取该键对应的值。注意,如果当前容器中没有以 key 为键的键值对,则其会使用该键向当前容器中插入一个新键值对。 -
at(key)
返回容器中存储的键 key 对应的值,如果 key 不存在,则会抛出 out_of_range 异常。 -
find(key)
查找以 key 为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(end 方法返回的迭代器)。 -
count(key)
在容器中查找以 key 键的键值对的个数。 -
equal_range(key)
返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中键为 key 的键值对所在的范围。 -
emplace
向容器中添加新键值对,直接在容器内部构造元素,避免了不必要的拷贝或移动,效率比insert
方法高。 -
emplace_hint
向容器中添加新键值对,接受一个迭代器position
作为提示参数,用于提高插入效率,但提示错误可能会降低效率。 -
insert
向容器中添加新键值对,通常需要先构造好value_type
对象,然后将其插入容器,可能会涉及额外的拷贝或移动操作。 -
erase
删除指定键值对。可以通过键、迭代器位置或迭代器范围来指定要删除的元素。 -
clear
清空容器,即删除容器中存储的所有键值对。
-
-
属性
empty
若容器为空,则返回 true;否则 false。size
返回当前容器中存有键值对的个数。max_size
返回容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
-
类函数
swap
交换 2 个 unordered_map 容器存储的键值对,前提是必须保证这 2 个容器的类型完全相等。
-
底层 hash 相关函数
bucket_count
返回当前容器底层存储键值对时,使用桶(一个线性链表代表一个桶)的数量。max_bucket_count
返回当前系统中,unordered_map 容器底层最多可以使用多少桶。bucket_size(n)
返回第 n 个桶中存储键值对的数量。bucket(key)
返回以 key 为键的键值对所在桶的编号。load_factor
返回 unordered_map 容器中当前的负载因子。负载因子,指的是的当前容器中存储键值对的数量(size)和使用桶数(bucket_count)的比值,即 load_factor = size / bucket_count。max_load_factor
返回或者设置当前 unordered_map 容器的负载因子。rehash(n)
将当前容器底层使用桶的数量设置为 n。reserve
将存储桶的数量(也就是 bucket_count 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。hash_function
返回当前容器使用的哈希函数对象。
1.3. 问题集
1.3.1. emplace、emplace_hint、insert 的区别
- 构造方式:
- insert:通常需要先构造好 value_type 对象,然后将其插入容器,可能会涉及额外的拷贝或移动操作。
- emplace 和 emplace_hint:直接在容器内部构造元素,避免了不必要的拷贝或移动,效率更高。
- 提示参数:
- insert 和 emplace:不需要提示参数。
- emplace_hint:接受一个迭代器作为提示参数,用于提高插入效率,但提示错误可能会降低效率。
- 返回值:
- insert 和 emplace:返回一个
std::pair<iterator, bool>
,表示插入是否成功。 - emplace_hint:返回一个指向新插入元素或已存在元素的迭代器。
- insert 和 emplace:返回一个
#include <iostream>
#include <unordered_map>int main() {std::unordered_map<int, std::string> myMap;auto result = myMap.insert({1, "one"});if (result.second) {std::cout << "Inserted successfully." << std::endl;} else {std::cout << "Element already exists." << std::endl;}return 0;
}
int main() {std::unordered_map<int, std::string> myMap;auto result = myMap.emplace(2, "two");if (result.second) {std::cout << "Emplaced successfully." << std::endl;} else {std::cout << "Element already exists." << std::endl;}return 0;
}
int main() {std::unordered_map<int, std::string> myMap;auto hint = myMap.begin();auto it = myMap.emplace_hint(hint, 3, "three");std::cout << "Inserted key: " << it->first << ", value: " << it->second << std::endl;return 0;
}
1.4. 参考链接
- C++ STL unordered_map容器用法详解