一、哈希表核心思想
哈希表又称散列表,核心逻辑:通过哈希函数,把关键字直接映射到数组下标 实现近乎 O(1) 时间复杂度的查找、插入、删除。
普通数组:按下标找值哈希表:按值直接找存储位置
二、哈希函数作用
公式:存储下标 = hash(关键字)设计原则:
- 计算速度快
- 映射分布均匀,减少扎堆
- 尽量减少重复映射结果
常用简单哈希:取模运算
hash(key) = key % 数组长度
三、哈希冲突
不同关键字,经过哈希函数算出同一个下标,即为哈希冲突。例如:长度为 10 数组,11 和 21 取模结果一致。
无法彻底避免,只能高效解决
四、两大主流冲突解决方式
1. 开放定址法
冲突后向后寻找空闲位置存放
- 线性探测:往后逐个遍历空位
- 二次探测:跳跃式寻找缺点:容易产生数据聚集,查找效率下降
2. 链地址法(拉链法)【工程最常用】
数组每个位置挂一条链表
- 映射下标相同的元素,全部挂载在同一条链表上
- 查询时先定位数组下标,再遍历短链表查找
- C++
unordered_map/unordered_set底层默认采用拉链法
五、负载因子
负载因子 = 元素个数 / 哈希表容量负载因子越大,冲突越多、效率越低C++ 无序容器默认达到阈值自动扩容重新哈希
六、C++ 哈希容器分类
unordered_set:无序集合,元素唯一,底层哈希表unordered_map:无序键值对,key 唯一,刷题最常用set/map:有序,底层红黑树,O (logn)
选型口诀
- 只需要快速查找、去重、存取无序 → 用哈希表 unordered
- 需要自动排序、有序遍历 → 用红黑树 map/set
七、unordered_map 全套刷题用法
头文件
#include <unordered_map>
#include <iostream>
#include <string>
using namespace std;
基础增删改查
int main()
{
unordered_map<string,int> mp;
// 插入
mp["张三"] = 20;
mp["李四"] = 22;
mp.insert({"王五",19});
// 取值
cout << mp["张三"] << endl;
// 修改
mp["张三"] = 25;
// 查找
if(mp.find("李四") != mp.end())
{
cout << "存在该键" << endl;
}
// 删除
mp.erase("王五");
// 遍历
for(auto& p : mp)
{
cout << p.first << " " << p.second << endl;
}
// 判空、大小
mp.empty();
mp.size();
return 0;
}
八、经典刷题例题:两数之和
LeetCode 1 原题,哈希表经典秒杀模板
#include <vector>
#include <unordered_map>
using namespace std;
vector<int> twoSum(vector<int>& nums, int target)
{
unordered_map<int,int> hash;
for(int i = 0; i < nums.size(); i++)
{
int need = target - nums[i];
if(hash.find(need) != hash.end())
{
return {hash[need],i};
}
hash[nums[i]] = i;
}
return {};
}
思路:边遍历边存值,寻找互补数字,一次遍历完成。
九、哈希表优缺点
优点
- 查找插入删除平均 O (1),速度极快
- 适合海量数据快速匹配、统计频次
缺点
- 无序存储,无法有序遍历
- 存在哈希冲突,极端情况退化成链表 O (n)
- 内存占用相对更高
十、面试高频考点
- 哈希表为什么查询快?哈希映射直接定位下标
- 哈希冲突怎么解决?拉链法、开放定址法
- unordered_map 和 map 底层区别与效率差异
- 负载因子作用与自动扩容机制
十一、今日总结
- 哈希表依靠哈希函数实现 O (1) 级访问
- 拉链法是实际开发最主流冲突解决方案
- 刷题优先使用 unordered_map 做频次统计、配对查找
- 无序选哈希,有序选红黑树容器