哈希表
哈希表
散列表,以 key-value 存储数据的数据结构。可以理解成一种高级数组 a[key] = value,数组的下标可以是大整数、浮点数等等。
哈希函数
如果 key 不是小整数,不能直接作为数组坐标,就需要使用哈希函数根据 key 来计算 value 的索引,即计算 value 存放的位置。例如 key 为大整数时可使用 \(x\bmod M\) 来作为索引。哈希函数为 f,那么键值对 (key,value) 应该放在 a[f(key)] 上,即对 key 做映射,映射到数组下标上。
冲突
如果对于任意 key,哈希函数计算出来的索引都不相同,那只用根据索引把 (key,value) 放到对应的位置就行了。但实际上,常常会出现两个不同的 key,他们用哈希函数计算出来的索引是相同的。这时候就需要一些方法来处理冲突。
拉链法
不同于数组,一个 a[i] 上保存一个链表,存储所有 \(f(key)=i\) 的 value。插入键值对时若多个 key 索引相同,则都插入到那个链表里;查询时对对应位置的链表扫描,比较里面的 key 和查询的 key 是否一致。注意我们计算了 f(key) 同时也保留了 key,f(key) 相同比较 key 和给定的 key 的过程不存在冲突。冲突存在于哈希函数间。
如果比较 key 的时间复杂度比较高,例如 string 间的比较,还可以使用哈希函数做另一个参数 pre。若一些键值对存在于 a[f(key)] 中,查找给定 key,则比较时先比较当前键值对的 pre 和给定 key 的 pre。也就是说定义另一个哈希函数 \(g(x)=pre\),插入键值对时保存 pre,查询某一 key 时也先计算 pre。这样避免了比较时间的浪费。
如果 \(f(x)\in[1,M]\),哈希表大小为 \(N\),那么一次插入/查询需要进行期望 \(O(\frac{N}{M})\) 次比较。
实现
给一个 key 为整数的实现:
const int kS = 1000003;
const int kM = 90007;
struct HashTable {
struct HashNode {
int key, value, nxt;
} data[kS];
int head[kM], cnt;
inline int f(int x) {
return (x % kM + kM) % kM;
} // 哈希函数根据 key 来设计
inline int get(int key) {
for(int i = head[f(key)]; i; i = data[i].nxt)
if(data[i].key == key) return data[i].value;
return -1;
}
inline void modify(int key, int value) {
for(int i = head[f(key)]; i; i = data[i].nxt)
if(data[i].key == key) return data[i].value = value, void();
}
inline void insert(int key, int value) {
if(get(key) != -1) return;
data[++cnt] = (HashNode){key, value, head[f(key)]};
head[f(key)] = cnt;
}
} mp;