文章目录
一、哈希表基础概念
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());
}
1167

被折叠的 条评论
为什么被折叠?



