在 OI 中,最常见的情况应该是键值为整数的情况。当键值的范围比较小的时候,可以直接把键值作为数组的下标,但当键值的范围比较大,比如以 范围内的整数作为键值的时候,就需要用到哈希表。一般把键值模一个较大的质数作为索引,也就是取 作为哈希函数。另一种比较常见的情况是 key 为字符串的情况,在 OI 中,一般不直接把字符串作为键值,而是先算出字符串的哈希值,再把其哈希值作为键值插入到哈希表里。
能为 key 计算索引之后,我们就可以知道每个键值对应的值 value 应该放在哪里了。假设我们用数组 a 存放数据,哈希函数是 f,那键值对 (key, value) 就应该放在 a[f(key)] 上。不论键值是什么类型,范围有多大,f(key) 都是在可接受范围内的整数,可以作为数组的下标。
冲突
如果对于任意的键值,哈希函数计算出来的索引都不相同,那只用根据索引把 (key, value) 放到对应的位置就行了。但实际上,常常会出现两个不同的键值,他们用哈希函数计算出来的索引是相同的。这时候就需要一些方法来处理冲突。在 OI 中,最常用的方法是拉链法。
// C++ VersionconstintSIZE=1000000;constintM=999997;structHashTable{structNode{intnext,value,key;}data[SIZE];inthead[M],size;intf(intkey){returnkey%M;}intget(intkey){for(intp=head[f(key)];p;p=data[p].next)if(data[p].key==key)returndata[p].value;return-1;}intmodify(intkey,intvalue){for(intp=head[f(key)];p;p=data[p].next)if(data[p].key==key)returndata[p].value=value;}intadd(intkey,intvalue){if(get(key)!=-1)return-1;data[++size]=(Node){head[f(key)],value,key};head[f(key)]=size;returnvalue;}};