一、核心概念
- 哈希表:通过哈希函数将键映射到存储位置,实现快速查找。
- 哈希冲突:不同键映射到同一位置。开放定址法通过探测下一个空位解决冲突。
- 负载因子:
a = 元素个数 / 表容量
,控制扩容时机(通常a ≥ 0.7
时扩容)。
二、代码结构解析
1. 哈希节点结构
enum STATE {
EXIST,
EMPTY,
DELETE
};
template<class K, class V>
struct HashData {
pair<K, V> _kv; // 存储键值对
STATE _state = EMPTY; // 状态标记:存在、空、删除
};
-
状态标记:
-
EXIST
:该位置有有效数据 -
EMPTY
:该位置为空 -
DELETE
:该位置数据被删除
-
-
作用:
-
解决冲突探测时无法区分“从未插入”和“已删除”的问题
-
删除时不物理删除数据,仅标记状态,避免破坏探测链
-
2. 哈希函数设计
- 默认哈希(整型):直接返回键值。
-
适用场景:整数类型(
int
,char
等) -
局限性:无法直接处理字符串等复杂类型
template<class K> struct DefaultHashFunc { size_t operator()(const K& key) { return (size_t)key; } };
-
- 字符串特化(BKDR哈希):减少冲突。
-
BKDR 算法:通过乘法和累加减少哈希冲突
-
必要性:字符串需要特殊处理,避免
str[0]
直接取首字符导致高冲突率template<> struct DefaultHashFunc<string> { size_t operator()(const string& str) { size_t hash = 0; for (auto ch : str) { hash *= 131; // BKDR 哈希乘子 hash += ch; } return hash; } };
-
3. 哈希表类
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable {
private:
vector<HashData<K, V>> _table; // 底层数组
size_t _n = 0; // 有效元素个数
};
-
动态数组:底层容器选择
vector
,支持动态扩容 -
模板参数:
-
K
:键类型,V
:值类型 -
HashFunc
:哈希函数类型,默认支持K
类型的哈希
-
三、关键操作实现步骤
1. 插入操作
- 步骤:
- 检查负载因子:超过阈值(如0.7)触发扩容。
- 扩容处理:创建新表,遍历旧表数据重新哈希插入。
- 线性探测插入:找到空位或删除位,插入数据并标记状态。
- 代码:
bool Insert(const pair<K, V>& kv) { // 扩容检查(载荷因子 ≥0.7) if (_n * 10 / _table.size() >= 7) { size_t newSize = _table.size() * 2; // 遍历旧表,重新映射到新表 HashTable<K, V, HashFunc> newHT; newHT._table.resize(newSize); // 遍历旧表的数据插入到新表即可 for (size_t i = 0; i < _table.size(); i++){ if (_table[i]._state == EXIST){ newHT.Insert(_table[i]._kv); } } _table.swap(newHT._table); } // 计算初始哈希位置 HashFunc hf; size_t hashi = hf(kv.first) % _table.size(); // 线性探测:找到空位置或可覆盖位置 while (_table[hashi]._state == EXIST) { ++hashi; hashi %= _table.size(); // 环形探测 } // 插入数据并标记状态 _table[hashi]._kv = kv; _table[hashi]._state = EXIST; ++_n; return true; }
-
线性探测:冲突时顺序向后查找空位
-
环形探测:
hashi %= _table.size()
保证索引不越界
-
2. 查找操作
- 步骤:
- 计算哈希值定位初始位置。
- 线性探测,直到遇到
EMPTY
状态。 - 检查每个
EXIST
状态的键是否匹配。
- 关键细节:
- 跳过删除状态:
DELETE
状态的位置需继续探测。
- 跳过删除状态:
- 代码:
HashData<const K, V>* Find(const K& key) { size_t hashi = hf(key) % _table.size(); while (_table[hashi]._state != EMPTY) { if (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key) { return (HashData<const K, V>*)&_table[hashi]; } ++hashi; hashi %= _table.size(); } return nullptr; }
3. 删除操作
- 步骤:
- 调用
Find
找到目标节点。 - 标记为
DELETE
状态(不实际移除数据),_n
递减。
- 调用
- 优势:逻辑删除避免破坏探测链。
- 代码:
bool Erase(const K& key) { HashData<const K, V>* ret = Find(key); if (ret) { ret->_state = DELETE; // 标记为删除 --_n; return true; } return false; }