《C++》哈希表解析与实现
文章目录
- 一、哈希表基础概念
- 1.1 什么是哈希表
- 1.2 为什么需要哈希表
- 1.3 哈希表的常见应用
- 二、哈希表核心原理
- 2.1 哈希函数设计
- 2.1.1 优秀哈希函数的特性
- 2.1.2 常见哈希函数
- 2.2 哈希冲突解决
- 2.2.1 开放定址法(闭散列)
- 2.2.2 链地址法 (C++ STL采用此方法)
- 2.3 装载因子与扩容
- 三、C++中的哈希表实现
- 3.1 STL中的unordered_map
- 3.2 自定义哈希函数
- 3.3 性能调优
- 四、手动实现哈希表
- 4.1 基础哈希表实现
- 4.2 扩容机制实现
- 4.3 迭代器实现
一、哈希表基础概念
1.1 什么是哈希表
-
哈希表(Hash Table)是一种使用哈希函数组织数据的数据结构,它通过把关键码值映射到表中一个位置来访问记录,以实现快速查找。简单来说,哈希表就是一个"键-值"对的集合,能够根据键快速找到对应的值。
-
哈希表的核心思想是:用空间换时间。它通过预先分配一个较大的数组,然后使用哈希函数将键转换为数组下标,从而实现平均时间复杂度为O(1)的查找、插入和删除操作。
1.2 为什么需要哈希表
在没有哈希表的情况下,我们通常使用数组或链表来存储数据:
- 数组:查找快(O(1)),但插入和删除慢(O(n))
- 链表:插入和删除快(O(1)),但查找慢(O(n))
哈希表结合了两者的优点,在大多数情况下都能提供O(1)时间复杂度的操作,极大地提高了数据处理的效率。
1.3 哈希表的常见应用
哈希表在编程中无处不在,典型应用包括:
- 字典实现:C++中的unordered_map
- 网页去重:检测重复URL
- 拼写检查:快速查找单词是否正确
二、哈希表核心原理
2.1 哈希函数设计
2.1.1 优秀哈希函数的特性
-
确定性:相同输入总是产生相同输出
-
均匀性:哈希值应均匀分布
-
高效性:计算速度快
-
抗碰撞性:不同输入产生相同输出的概率低
2.1.2 常见哈希函数
-
直接定址法:Hash(key) = a*key + b
-
除留余数法:Hash(key) = key % p(p最好是质数)
-
平方取中法:取key平方的中间几位
-
折叠法:将key分成几部分后相加
-
随机数法:Hash(key) = random(key)
2.2 哈希冲突解决
2.2.1 开放定址法(闭散列)
-
线性探测:依次检查下一个位置
-
二次探测:使用二次方程计算探测位置
-
双重哈希:使用第二个哈希函数
2.2.2 链地址法 (C++ STL采用此方法)
-
每个桶位置维护一个链表
-
冲突元素追加到链表末尾
2.3 装载因子与扩容
装载因子(Load Factor)是哈希表中已存储元素数量与哈希表大小的比值。当装载因子超过某个值(通常为0.7-0.8)时,哈希表的性能会显著下降,此时需要进行扩容。扩容的操作包括以下步骤:
- 创建一个更大的哈希表(通常是原大小的2倍)
- 重新计算所有元素的哈希值并插入新表
- 释放原表的空间
三、C++中的哈希表实现
3.1 STL中的unordered_map
C++11引入了unordered_map,它是基于哈希表实现的关联容器,使用非常方便:
#include <unordered_map>
#include <string>std::unordered_map<std::string, int> wordCount;// 插入元素
wordCount["apple"] = 5;
wordCount.insert({"banana", 3});// 访问元素
std::cout << "apple count: " << wordCount["apple"] << std::endl;// 遍历
for (const auto& pair : wordCount) {std::cout << pair.first << ": " << pair.second << std::endl;
}
3.2 自定义哈希函数
当使用自定义类型作为键时,需要提供哈希函数和相等比较函数:
struct Person {std::string name;int age;
};// 自定义哈希函数
struct PersonHash {size_t operator()(const Person& p) const {return std::hash<std::string>()(p.name) ^ std::hash<int>()(p.age);}
};// 自定义相等比较
struct PersonEqual {bool operator()(const Person& p1, const Person& p2) const {return p1.name == p2.name && p1.age == p2.age;}
};std::unordered_map<Person, std::string, PersonHash, PersonEqual> personMap;
3.3 性能调优
unordered_map提供了一些调整性能的参数:
- bucket_count:桶的数量
- load_factor:当前装载因子
- max_load_factor:最大允许装载因子
std::unordered_map<std::string, int> myMap;// 预留空间,避免频繁扩容
myMap.reserve(1000);// 设置最大装载因子
myMap.max_load_factor(0.75);// 获取桶数量
std::cout << "Bucket count: " << myMap.bucket_count() << std::endl;
四、手动实现哈希表
4.1 基础哈希表实现
#include <vector>
#include <list>template <typename K, typename V>
class HashTable {
private:struct KeyValue {K key;V value;KeyValue(const K& k, const V& v) : key(k), value(v) {}};std::vector<std::list<KeyValue>> table;size_t size;size_t hash(const K& key) const {return std::hash<K>()(key) % table.size();}public:HashTable(size_t initialSize = 101) : table(initialSize), size(0) {}void insert(const K& key, const V& value) {size_t index = hash(key);for (auto& kv : table[index]) {if (kv.key == key) {kv.value = value;return;}}table[index].emplace_back(key, value);size++;}bool find(const K& key, V& value) const {size_t index = hash(key);for (const auto& kv : table[index]) {if (kv.key == key) {value = kv.value;return true;}}return false;}void remove(const K& key) {size_t index = hash(key);for (auto it = table[index].begin(); it != table[index].end(); ++it) {if (it->key == key) {table[index].erase(it);size--;return;}}}
};
4.2 扩容机制实现
添加自动扩容功能,当装载因子过高时自动扩展哈希表:
void checkLoadFactor() {double loadFactor = static_cast<double>(size) / table.size();if (loadFactor > 0.7) {rehash(table.size() * 2);}
}void rehash(size_t newSize) {std::vector<std::list<KeyValue>> newTable(newSize);for (auto& bucket : table) {for (auto& kv : bucket) {size_t newIndex = std::hash<K>()(kv.key) % newSize;newTable[newIndex].push_back(kv);}}table = std::move(newTable);
}
4.3 迭代器实现
为哈希表添加迭代器支持,使其更符合STL风格:
class iterator {typename std::vector<std::list<KeyValue>>::iterator vecIt;typename std::list<KeyValue>::iterator listIt;std::vector<std::list<KeyValue>>* table;public:iterator(std::vector<std::list<KeyValue>>* t, typename std::vector<std::list<KeyValue>>::iterator vi,typename std::list<KeyValue>::iterator li): table(t), vecIt(vi), listIt(li) {}KeyValue& operator*() { return *listIt; }KeyValue* operator->() { return &(*listIt); }iterator& operator++() {++listIt;if (listIt == vecIt->end()) {++vecIt;while (vecIt != table->end() && vecIt->empty()) {++vecIt;}if (vecIt != table->end()) {listIt = vecIt->begin();}}return *this;}bool operator!=(const iterator& other) const {return vecIt != other.vecIt || listIt != other.listIt;}
};iterator begin() {auto vecIt = table.begin();while (vecIt != table.end() && vecIt->empty()) {++vecIt;}if (vecIt != table.end()) {return iterator(&table, vecIt, vecIt->begin());}return end();
}iterator end() {return iterator(&table, table.end(), typename std::list<KeyValue>::iterator());
}