通过哈希函数实现常数时间复杂度的高效数据结构,键值对存储的经典解决方案
哈希表(Hash Table),也称散列表,是一种通过哈希函数将键(Key)映射到存储位置的数据结构,能够在平均 O(1) 时间内完成插入、删除和查找操作。
哈希函数是哈希表的核心,负责将任意键转换为有效的数组索引。一个好的哈希函数应满足:计算简单、分布均匀、冲突少。
// 整数哈希:除法取余
function hashInt(key, tableSize):
return key % tableSize
// 字符串哈希:多项式滚动哈希
function hashString(str, tableSize):
hash = 0
p = 31 // 质数基数
for each char in str:
hash = (hash * p + char) % tableSize
return hash
当两个不同的键被哈希到相同位置时,称为哈希冲突。冲突不可避免(鸽巢原理),但可通过以下策略处理:
每个桶存储一个链表,冲突的元素追加到链表中。Java HashMap、Go map 等均采用此方法(可能结合红黑树优化)。
所有元素存储在数组中,冲突时按某种探测序列寻找下一个空位。
// 线性探测插入
function insertLinearProbe(table, key, value):
index = hash(key)
while table[index] != null:
if table[index].key == key:
table[index].value = value // 更新
return
index = (index + 1) % tableSize // 线性探测
table[index] = (key, value)
| 特性 | 链地址法 | 开放寻址法 |
|---|---|---|
| 空间利用 | 需额外链表空间 | 全部在数组内 |
| 删除操作 | 简单,删链表节点 | 需特殊标记(墓碑) |
| 缓存友好 | 较差(指针跳转) | 较好(连续内存) |
| 负载因子 | 可大于 1 | 必须小于 1 |
| 实现复杂度 | 中等 | 较复杂 |
function put(key, value):
index = hash(key) % tableSize
bucket = table[index]
// 检查是否已存在
for entry in bucket:
if entry.key == key:
entry.value = value // 更新值
return
// 插入新条目
bucket.append((key, value))
size++
// 检查是否需要扩容
if size / tableSize > loadFactor:
resize()
function get(key):
index = hash(key) % tableSize
bucket = table[index]
for entry in bucket:
if entry.key == key:
return entry.value
return null // 未找到
function remove(key):
index = hash(key) % tableSize
bucket = table[index]
for i, entry in enumerate(bucket):
if entry.key == key:
bucket.removeAt(i)
size--
return true
return false
| 操作 | 平均时间 | 最坏时间 | 空间 |
|---|---|---|---|
| 插入 (Put) | O(1) | O(n) | O(1) |
| 查找 (Get) | O(1) | O(n) | O(1) |
| 删除 (Remove) | O(1) | O(n) | O(1) |
| 扩容 (Resize) | - | O(n) | O(n) |
当负载因子超过阈值(如 0.75)时,需要扩容以保持性能。扩容涉及创建更大的数组并重新哈希所有元素。
function resize():
oldTable = table
newSize = tableSize * 2
table = new Array[newSize]
tableSize = newSize
size = 0
// 重新插入所有元素
for bucket in oldTable:
for entry in bucket:
put(entry.key, entry.value)
import java.util.LinkedList;
public class HashTable<K, V> {
private static final int DEFAULT_CAPACITY = 16;
private static final float LOAD_FACTOR = 0.75f;
private LinkedList<Entry<K, V>>[] buckets;
private int size;
private static class Entry<K, V> {
K key;
V value;
Entry(K key, V value) {
this.key = key;
this.value = value;
}
}
@SuppressWarnings("unchecked")
public HashTable() {
buckets = new LinkedList[DEFAULT_CAPACITY];
for (int i = 0; i < DEFAULT_CAPACITY; i++) {
buckets[i] = new LinkedList<>();
}
size = 0;
}
private int hash(K key) {
return Math.abs(key.hashCode()) % buckets.length;
}
public void put(K key, V value) {
int index = hash(key);
LinkedList<Entry<K, V>> bucket = buckets[index];
// 检查是否存在
for (Entry<K, V> entry : bucket) {
if (entry.key.equals(key)) {
entry.value = value;
return;
}
}
// 插入新条目
bucket.add(new Entry<>(key, value));
size++;
// 检查是否需要扩容
if ((float) size / buckets.length > LOAD_FACTOR) {
resize();
}
}
public V get(K key) {
int index = hash(key);
for (Entry<K, V> entry : buckets[index]) {
if (entry.key.equals(key)) {
return entry.value;
}
}
return null;
}
public boolean remove(K key) {
int index = hash(key);
LinkedList<Entry<K, V>> bucket = buckets[index];
for (int i = 0; i < bucket.size(); i++) {
if (bucket.get(i).key.equals(key)) {
bucket.remove(i);
size--;
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
private void resize() {
LinkedList<Entry<K, V>>[] oldBuckets = buckets;
buckets = new LinkedList[oldBuckets.length * 2];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new LinkedList<>();
}
size = 0;
for (LinkedList<Entry<K, V>> bucket : oldBuckets) {
for (Entry<K, V> entry : bucket) {
put(entry.key, entry.value);
}
}
}
public int size() { return size; }
public boolean isEmpty() { return size == 0; }
}
class HashTable:
def __init__(self, capacity=16, load_factor=0.75):
self.capacity = capacity
self.load_factor = load_factor
self.size = 0
self.buckets = [[] for _ in range(capacity)]
def _hash(self, key):
return hash(key) % self.capacity
def put(self, key, value):
index = self._hash(key)
for i, (k, v) in enumerate(self.buckets[index]):
if k == key:
self.buckets[index][i] = (key, value)
return
self.buckets[index].append((key, value))
self.size += 1
if self.size / self.capacity > self.load_factor:
self._resize()
def get(self, key, default=None):
index = self._hash(key)
for k, v in self.buckets[index]:
if k == key:
return v
return default
def remove(self, key):
index = self._hash(key)
for i, (k, v) in enumerate(self.buckets[index]):
if k == key:
self.buckets[index].pop(i)
self.size -= 1
return True
return False
def _resize(self):
old_buckets = self.buckets
self.capacity *= 2
self.buckets = [[] for _ in range(self.capacity)]
self.size = 0
for bucket in old_buckets:
for key, value in bucket:
self.put(key, value)
| 特性 | 哈希表 | 红黑树/AVL | 跳表 | 数组 |
|---|---|---|---|---|
| 查找 | O(1) 平均 | O(log n) | O(log n) | O(n) |
| 插入 | O(1) 平均 | O(log n) | O(log n) | O(n) |
| 有序遍历 | 不支持 | 支持 | 支持 | 支持 |
| 范围查询 | 不支持 | 支持 | 支持 | O(n) |
| 空间效率 | 中等 | 较好 | 中等 | 最优 |
| 实现复杂度 | 简单 | 复杂 | 中等 | 最简单 |
MySQL Memory 引擎使用哈希索引实现等值查询的 O(1) 性能,适用于精确匹配场景。
Redis、Memcached 等缓存系统的核心数据结构,支持快速的键值存取。
Java HashMap、Python dict、Go map、JavaScript Object 等语言级数据结构的底层实现。
编译器中的符号表用于快速查找变量、函数的定义和属性信息。
统计词频、检测重复元素、投票计数等场景的高效实现。
一致性哈希用于分布式缓存、负载均衡中的数据分片和路由。
网络设备中的路由查找表,快速匹配目标 IP 地址对应的转发端口。
将密码哈希后存储,验证时比对哈希值,避免明文泄露风险。
传统哈希在节点增减时需重新映射大量数据。一致性哈希将哈希空间组织成环形,节点变动只影响相邻区间的数据,广泛用于分布式缓存(如 Memcached)、负载均衡和分布式存储。
一种空间效率极高的概率型数据结构,用于判断元素是否存在。可能误判(假阳性),但不会漏判(假阴性)。
对于静态键集合,可构造无冲突的哈希函数,实现严格 O(1) 查找。常用于编译器关键字识别、只读字典等场景。
使用两个哈希函数和两个表,插入时若位置被占则“踢走”原元素到其备选位置,查找最坏 O(1)。适用于高性能网络设备中的查找表。
体验哈希表的插入、查找、删除操作,观察键值对的存储分布: