哈希表
用途
什么时候需要考虑使用哈希表呢?
针对一个集合,我们若想快速判断一个元素是否存在于这个集合中时,就应当考虑哈希法。(如查找一个key值是否在我们想要的集合中:学生名是否在学校的名单里)哈希表查找key的时间复杂度为O(1),可以近似为随机读取。
当使用哈希表来解决问题时,一般有三种选择:
1.数组,如要区分一个仅包含小写字母字符串s中各字符的数目,可以使用s[i] - 'a'(ascii码的妙用),将其映射到为索引0-25的一个数组上,这种映射方式是有效且高效的。有效的字母异位词。数组,简单哈希表https://programmercarl.com/0242.%E6%9C%89%E6%95%88%E7%9A%84%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.html#%E6%80%9D%E8%B7%AF代码随想录 (programmercarl.com) 赎金信https://programmercarl.com/0383.%E8%B5%8E%E9%87%91%E4%BF%A1.html#%E6%80%9D%E8%B7%AF
2.set(集合),用于存储独一无二的元素,其中元素是无序的,查找、添加和删除操作的时间复杂度均为O(1)。它通常用于检查某个元素是否存在于集合中,或者用于去除重复元素。
代码随想录 (programmercarl.com) 两个数组的交集https://programmercarl.com/0349.%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE代码随想录 (programmercarl.com) 快乐数https://programmercarl.com/0202.%E5%BF%AB%E4%B9%90%E6%95%B0.html#%E6%80%9D%E8%B7%AF
3.map(映射),map是一个键值对集合,用于存储键和值的关联,若使用哈希表实现,查找、添加和删除操作的时间复杂度为O(1)。map中元素是无序的键值对,键key是独一无二的,而value值可以重复。它通常用于根据键来快速查找、添加或删除对应的值。
代码随想录 (programmercarl.com) 两数之和https://programmercarl.com/0001.%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.html#%E6%80%9D%E8%B7%AF代码随想录 (programmercarl.com)https://programmercarl.com/0454.%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0II.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE
实现
在C++中,哈希表主要使用std::unordered_map和std::unordered_set这两个容器实现。
1.std::unordered::map:
- 关联容器,用于存储键值对,其中键是唯一的,而值可以重复。
- 使用哈希表实现,能提供快速的查找、插入和删除操作,时间复杂度为O(1)。
- 键的哈希值用于确定存储位置,如果发生哈希冲突,容器会使用链表或其他数据结构来处理冲突。
2.std::unordered::set:
- 集合容器,用于存储唯一的元素。
- 使用哈希表实现,因此通常提供快速的查找、插入和删除操作,时间复杂度为O(1)。
- 元素的哈希值用于确定存储位置,处理冲突的方式与std::unordered_map类似。
除此之外,C++还提供了其他基于哈希表的容器,如std::unordered_multimap和std::unordered_multiset,它们允许键或值有多个实例(允许键值冲突),通常底层实现为红黑树,查找、插入和删除的效率为O(logn)。
基本操作
unordered_map
//创建unordered_map
std::unordered_map<int, std::string> umap = {
{1, "one"},
{2, "two"},
{3, "three"}
};
//插入元素
umap.insert(std::make_pair(4, "four")); // 使用 pair
umap[5] = "five"; // 使用下标运算符
//查找元素
auto it = umap.find(2); // 返回一个迭代器,如果找到则指向元素,否则指向 end()
if (it != umap.end()) {
std::cout << "Found: " << it->second << std::endl;
}
//访问元素
std::string value = umap[2]; // 如果键不存在,会创建一个默认构造的值
std::string value = umap.at(2); // 如果键不存在,抛出 std::out_of_range 异常
//删除元素
umap.erase(2); // 删除键为 2 的元素
//检查元素是否存在
if (umap.count(3) > 0) {
std::cout << "Key 3 exists" << std::endl;
}
//遍历元素
for (const auto& pair : umap) {
std::cout << pair.first << " -> " << pair.second << std::endl;
}
//获取容器大小
size_t size = umap.size(); // 返回元素的数量
//检查容器是否为空
bool empty = umap.empty(); // 如果容器为空,返回 true
//清空容器
umap.clear(); // 删除所有元素
//获取桶的数量
size_t bucket_count = umap.bucket_count(); // 返回桶的数量
//获取特定键的桶索引
size_t bucket_index = umap.bucket(2); // 返回键为 2 的元素的桶索引
在 C++ 的 std::unordered_map
中,“桶”(bucket)是用于存储具有相同或相近哈希值的关键字的内部容器。每个桶包含了一系列的元素,这些元素的哈希值在经过哈希表的哈希函数处理后,映射到了同一个桶上。
当哈希表中的元素增多时,为了保持性能,哈希表会根据需要增加桶的数量,这个过程称为重新哈希(rehashing)。重新哈希会导致所有现有的元素被重新分配到新的桶中,这是一个相对昂贵的操作。
获取桶的数量意味着查询哈希表当前有多少个这样的桶。这个信息可以用于分析哈希表的性能和效率。例如,如果一个哈希表有很多元素但桶的数量很少,可能会导致大量的哈希冲突,从而降低性能。相反,如果桶的数量远大于元素的数量,可能会浪费空间。
unordered_set
set基本与map的操作类似,但需要注意的是set只保存键
//创建unordered_set
std::unordered_set<int> uset = {1, 2, 3};
//插入元素
uset.insert(4); // 插入元素 4
//查找元素
auto it = uset.find(2); // 返回一个迭代器,如果找到则指向元素,否则指向 end()
if (it != uset.end()) {
std::cout << "Found: " << *it << std::endl;
}
//检查元素是否存在
if (uset.count(3) > 0) {
std::cout << "Element 3 exists" << std::endl;
}
//删除元素
uset.erase(2); // 删除元素 2
//遍历元素
for (const int& value : uset) {
std::cout << value << std::endl;
}
//获取容器大小
size_t size = uset.size(); // 返回元素的数量
//检查容器是否为空
bool empty = uset.empty(); // 如果容器为空,返回 true
//清空容器
uset.clear(); // 删除所有元素
//获取桶的数量
size_t bucket_count = uset.bucket_count(); // 返回桶的数量
//获取特定元素的桶索引
size_t bucket_index = uset.bucket(2); // 返回元素 2 的桶索引