哈希表

哈希表

散列表,以 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;
posted @ 2020-10-14 19:28  Any%  阅读(93)  评论(0编辑  收藏  举报