🧠 引言
你知道吗?
JS 中的 Map
、对象 {}
、Python 的 dict
、Java 的 HashMap
背后都藏着一个共同的“魔法结构”——哈希表(Hash Table) 。
今天我们不光要图解它的原理,还要手撸一个迷你版哈希表,并搞懂「哈希冲突」「链地址法」「开放地址法」这些“传说中”的概念。
🔍 一、什么是哈希表?
哈希表是一种通过哈希函数将“键”映射到“数组索引”的数据结构。
特点:
操作 | 时间复杂度 |
---|---|
插入 | O(1)(理想) |
查询 | O(1)(理想) |
删除 | O(1)(理想) |
🧠 二、图解哈希原理
key: "apple" → 哈希函数 → index: 2 → 存在数组[2]位置
📌 你只需要记住:
键(key) ➡ 哈希函数 ➡ 数组下标 ➡ 存值(value)
🧱 三、JS手写一个哈希表(链地址法)
✅ 1. 哈希函数(极简版)
function hash(key, size) {
let hashCode = 0;
for (let i = 0; i < key.length; i++) {
hashCode += key.charCodeAt(i);
}
return hashCode % size;
}
✅ 2. 哈希表类实现(带冲突处理)
class HashTable {
constructor(size = 7) {
this.table = new Array(size);
}
set(key, value) {
const index = hash(key, this.table.length);
if (!this.table[index]) this.table[index] = [];
for (let pair of this.table[index]) {
if (pair[0] === key) {
pair[1] = value;
return;
}
}
this.table[index].push([key, value]);
}
get(key) {
const index = hash(key, this.table.length);
const bucket = this.table[index];
if (!bucket) return undefined;
for (let pair of bucket) {
if (pair[0] === key) return pair[1];
}
return undefined;
}
print() {
console.log(this.table);
}
}
✅ 3. 使用示例
const map = new HashTable();
map.set('apple', 100);
map.set('banana', 200);
map.set('grape', 300);
map.set('papple', 400); // 冲突概率提高
map.print();
console.log(map.get('banana')); // 输出 200
🧪 四、哈希冲突的两种解决方式
✅ 1. 链地址法(Chaining)
- 用数组或链表存冲突元素
- 示例中
this.table[index]
是个数组
✅ 2. 开放地址法(Open Addressing)
-
找下一个空位插入,常见策略:
- 线性探测:+1、+2…
- 二次探测:+1²、+2²…
- 双重哈希
(此处暂不展开,后续有专文详解)
⚠️ 五、常见误区与易错点
错误行为 | 正确做法 |
---|---|
哈希函数写得太随意 | 保证分布均匀 + 尽量减少冲突 |
没处理冲突 | 最少需要链地址法 or 开放地址法 |
删除元素后未处理空洞 | 在开放地址法中需要特殊处理 |
使用对象当键 | JS 中对象键默认会被转为字符串 |
🔄 六、JS 中的对象和 Map 区别?
对比点 | 对象 {} | Map |
---|---|---|
键类型 | 只能是字符串或Symbol | 任意类型(包括对象) |
顺序保证 | 无 | 有插入顺序 |
迭代性能 | 差 | 高 |
内部结构 | 哈希表 | 哈希表 + 内部优化 |
🔧 七、拓展任务
- 自己实现开放地址法哈希表(使用线性探测)
- 编写一个「对象去重」工具(基于哈希)
- 模拟 JS
Map
支持任意对象键值存储
🧩 总结一句话
哈希表是现代语言背后的“黑科技”,也是面试题高频考点。掌握它,就等于掌握了高效存储与查找的核心武器!
下一篇预告:
📘 第5篇:【二叉树你还看不懂?】一文讲清二叉树的遍历、构造与应用!