本篇 核心知识点:哈希表核心理论(哈希函数、哈希冲突);冲突四大解决办法;C++11 unordered_map 底层;手写拉链哈希表;容器选型对比
一、哈希表(散列表)核心理论
1 哈希表概念
概念:利用哈希函数将键 Key 映射到一段连续数组下标,直接定位存储位置,实现近似 O (1) 增删查;类比字典拼音 / 部首索引,无需全局遍历。
特性:底层基础载体是连续数组(顺序存储);C++11 unordered_map/unordered_set 底层哈希表。
2 哈希函数
概念:输入键 Key,通过固定数学运算输出数组下标映射关系。
核心设计要求:运算速度快、冲突概率尽可能低。
主流哈希函数
(1)直接定址法
公式:hash(Key) = a * Key + b
特性:
-
一对一映射,完全无哈希冲突;
-
缺点:需要极大连续数组空间,空间利用率极低;
-
适用:键值连续规整的场景。
(2)除留余数法(最常用)
公式:hash(Key) = Key % P
特性:
-
P 建议选取小于数组长度的质数,降低冲突概率;
-
实现简单运算快,手写哈希表首选;
-
缺点:数据分布不均时冲突大量产生。
拓展:
1.数字分析法
选取关键字中分布均匀、随机性强的几位数字作为哈希地址,避开重复集中的数字段,适合关键字位数固定、数字分布可预判的场景。
2.平方取中法
将关键字做平方运算,截取平方结果中间若干位作为哈希值;平方后中间数字随机性更高,适合关键字分布零散、无明显规律的情况。
3.折叠法
把关键字分割成长度相等的几段,将各段数值相加,取和的低位作为哈希地址,用于关键字位数远大于哈希表地址位数的场景。
4.随机数法
借助随机函数,以关键字为参数生成随机数映射为哈希地址,随机性最好,适合关键字长度差异大、分布无规律的数据。
3 哈希冲突
概念
多个不同 Key,经过哈希函数计算得到同一个数组下标,称为哈希冲突。
影响因素
-
哈希函数质量;
-
哈希表数组长度;
-
装填因子 = 表内元素总数 / 数组总长度;装填因子越大,冲突概率越高。
4 四大哈希冲突解决方案
方案 1:开放定址法(原地探测)
概念
冲突后在当前数组向后寻找空下标,原地存放数据,不额外开辟内存。
细分:线性探测、二次探测、再哈希探测。
特性:无需额外链表,但删除操作会产生空位干扰查找,易堆积冲突。
方案 2:拉链法(链地址法,STL 标准实现)
概念
数组每个下标对应一条单向链表;同一哈希下冲突数据全部挂在对应链表尾部。
特性
-
空间利用率高,仅冲突节点额外分配;
-
增删查找逻辑清晰,是
unordered_map底层方案; -
链表过长会退化至 O (n) 遍历,需控制装填因子扩容。
手写拉链哈希表代码示例
#include <iostream>
#include <list>
using namespace std;
// 哈希表容量
#define TABLE_SIZE 23
// 键值对结构体
struct Pair{
int key;
int val;
Pair(int k,int v):key(k),val(v){}
};
// 拉链哈希表
class HashTable{
private:
list<Pair> table[TABLE_SIZE];
// 哈希函数:除留余数
int hashFunc(int key){
return key % TABLE_SIZE;
}
public:
// 插入
void insert(int k, int v){
int idx = hashFunc(k);
table[idx].emplace_back(k,v);
}
// 查找
bool find(int k, int& outVal){
int idx = hashFunc(k);
for(auto& p : table[idx]){
if(p.key == k){
outVal = p.val;
return true;
}
}
return false;
}
// 删除
void erase(int k){
int idx = hashFunc(k);
for(auto it = table[idx].begin(); it != table[idx]; it++){
if(it->key == k){
table[idx].erase(it);
return;
}
}
}
};
方案 3 公共溢出区法
概念:主数组只存无冲突数据,冲突全部存入单独溢出数组;
缺点:大量冲突时溢出数组遍历效率极低,工程极少使用。
方案 4 再哈希法
概念:冲突时切换另一套哈希函数重新计算下标,多次计算直至空位;
缺点:多次哈希运算,查找速度下降。
二、STL 哈希容器 unordered_map /unordered_set
1 底层特性
-
C++98 无哈希容器,C++11 新增;
-
底层采用
拉链法哈希表,对比 map红黑树:
容器 底层 有序性 平均复杂度 适用场景 map 红黑树 自动升序 O (logn) 需要有序遍历、范围查找 unordered_map 哈希表 无序 O (1) 海量数据单点快速查询
2 unordered_map 核心特性
-
存储
pair<Key,Val>键值对,Key 唯一; -
无
push_back,仅insert/[]插入; -
自定义 Key 需提供哈希函数重载,否则编译报错;
代码示例
#include <unordered_map>
#include <iostream>
#include <string>
using namespace std;
int main(){
unordered_map<string, int> equip;
// 下标插入,键不存在自动创建
equip["长剑"] = 120;
// insert插入,重复键无效
equip.insert(pair<string,int>("法杖",95));
// 遍历
for(auto& item : equip)
cout << item.first << " 攻击:" << item.second << endl;
// 查找
if(equip.find("长剑") != equip.end())
cout << "武器存在" << endl;
return 0;
}