哈希表是什么
哈希表(或叫散列表)是一种以键-值(key-value)存储数据的结构,也就是说,把它当成一个黑盒子的话,我们输入查找的键(key),即可找到其对应的值(value)。
可以拿查字典做类比,当我们输入一个字时,需要得到它的页码,这里的字就是键(key),而页码就是值(value)。
那哈希表与普通的表有什么不一样呢?为什么要使用哈希表呢?
想想在查字典的时候经常会有因为字典太厚而难以找到的情况吧?这时候如何快速翻到想找的字呢——字典作者在字典的起始便为我们提供了拼音检字表和部首检字表。以拼音检字表为例,当我们通过“字->拼音->字的页码”这样一个流程去查字典的时候,我们便已经在使用哈希表了。
可以认为,哈希表与普通表的区别便是增加了索引,也就是例子里的“拼音检字表”,一个好的索引能够大大加快查询的速度。下面将详细地把“字->拼音->字的页码”翻译为哈希表这一数据结构里的各个步骤。
“字->拼音”
哈希表中,选择不同的索引会导致不同的查询速度,就像“部首”与“拼音”是字典的两种索引,对同一个字,使用这两种索引导致的查询速度是不同的。那么哈希表中索引应该如何生成,怎样的索引才是更好的呢?
在哈希表中,将这样一种把任意长度的消息压缩到某一固定长度的消息摘要的函数称为“散列函数”,也称为“哈希函数”。一般来说,散列函数满足以下的条件:
- 对输入值运算,得到一个固定长度的摘要(Hash value);
- 不同的输入值可能对应同样的输出值;
散列函数有很多种,比如对于正整数我们可以采用除留余数法,把余数作为索引,比如对一组数除以 16 得到索引:
除留余数法
另外还可以有平方散列法、斐波那契散列法等等。又比如在下载文件或者程序时,往往能够见到下载页面有作者提供的 MD5 值或者 SHA1 值。如果下载下来的文件 MD5/SHA1 值与作者提供的不同,我们就可以确定文件被篡改过了。这里所提到的 MD5 与 SHA1 便是两种哈希算法。
好的散列函数往往需要满足下面几点:
- 散列函数的输出值尽量接近均匀分布;
- x 的微小变化可以使 f(x) 发生非常大的变化,即所谓“雪崩效应”(Avalanche effect);
这是为了减少“哈希冲突”(Hashcollision),也就是两个不同输入产生了相同输出值的情况。
“拼音->字的页码”
在查阅拼音检字表的时候,一个拼音会对应多个汉字,在这种情况下就需要一种方法处理这种冲突。一种比较直接的方法就是拉链法:它将每个索引指向一条链表,链表中的每一个节点都存储哈希值为当前索引的键值对,将前面除留余数法的例子用拉链法表示就如下图所示:

拉链法示例
当查询一个数字的时候就可以先取余,得到索引,再根据索引指向的链表逐个寻找到它的位置。
当然,处理冲突的方式还有很多,比如双散列、开放地址法、建立公共溢出区等等,这里不再详细介绍。