哈希表

379 阅读3分钟

哈希表

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。


哈希冲突的解决策略

  1. 开放寻址法(Open Addressing)

通常采用的冲突解决策略为开放寻址法(Open Addressing),将所有的元素都存放在哈希表内的数组中,不使用额外的数据结构。

开放寻址法的最简单的一种实现就是线性探查(Linear Probing),步骤如下:

  1. 当插入新的元素时,使用哈希函数在哈希表中定位元素位置;
  2. 检查哈希表中该位置是否已经存在元素。如果该位置内容为空,则插入并返回,否则转向步骤 3。
  3. 如果该位置为 i,则检查 i+1 是否为空,如果已被占用,则检查 i+2,依此类推,直到找到一个内容为空的位置。

线性探查(Linear Probing)方式虽然简单,但并不是解决冲突的最好的策略,因为它会导致同类哈希的聚集(Primary Clustering)。这导致搜索哈希表时,冲突依然存在。

2. 二次探查

每次检查位置空间的步长为平方倍数。也就是说,如果位置 s 被占用,则首先检查 s + 12 处,然后检查s - 12,s + 22,s - 22,s + 32 依此类推,而不是象线性探查那样以 s + 1,s + 2 ... 方式增长。尽管如此,二次探查同样也会导致同类哈希聚集问题(Secondary Clustering)。

3. 二度哈希

有一个包含一组哈希函数 H1...Hn 的集合。当需要从哈希表中添加或获取元素时,首先使用哈希函数 H1。如果导致冲突,则尝试使用 H2,以此类推,直到 Hn。所有的哈希函数都与 H1 十分相似,不同的是它们选用的乘法因子(multiplicative factor)。

4. 链接技术(chaining)

链接技术(chaining)是一种冲突解决策略(Collision Resolution Strategy)。在链接法中,把哈希到同一个槽中的所有元素都放到一个链表中。

使用探查技术(probing)时,如果发生冲突,则将尝试列表中的下一个位置。如果使用二度哈希(rehashing),则将导致所有的哈希被重新计算。而链接技术(chaining)将采用额外的数据结构来处理冲突,其将哈希表中每个位置(slot)都映射到了一个链表。当冲突发生时,冲突的元素将被添加到桶(bucket)列表中,而每个桶都包含了一个链表以存储相同哈希的元素。

上图中的哈希表包含了 8 个桶(bucket),也就是自顶向下的黄色背景的位置。如果一个新的元素要被添加至哈希表中,将会被添加至其 Key 的哈希所对应的桶中。如果在相同位置已经有一个元素存在了,则将会将新元素添加到列表的前面。

使用链接技术添加元素的操作涉及到哈希计算和链表操作,但其仍为常量,渐进时间为 O(1)。而进行查询和删除操作时,其平均时间取决于元素的数量和桶(bucket)的数量。具体的说就是运行时间为 O(n/m),这里 n 为元素的总数量,m 是桶的数量。但通常对哈希表的实现几乎总是使 n = O(m),也就是说,元素的总数绝不会超过桶的总数,所以 O(n/m) 也变成了常量 O(1)。