哈希表

哈希表

  哈希表是根据关键码(key)直接进行访问的数据结构。通过将key通过特定映射函数映射到某一位置直接存储、访问。
  哈希表查找的时间复杂度最好时可以为O(1)。在最坏情况下,哈希表会退化为单链表,此时时间复杂度为O(n)。

  • 若关键字为k,则其值存放在f(k)的存储位置上,称这个对应关系f为散列函数
  • 对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为碰撞,我们必须处理哈希碰撞
  • 若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少碰撞

哈希函数

  哈希表通过哈希函数将元素映射到不同的位置,因此哈希函数的选择对整个哈希表操作的性能影响非常大。哈希函数的选择有非常多种方法:
  1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数。若H(key)中已经有值了,就往下一个找,直到H(key)中没有值了,就放进去。
  2. 数字分析法:分析数据,选择冲突的几率明显较小的几位作为散列地址
  3. 平方取中法:取关键字平方后的中间几位为哈希地址
  4. 折叠法:将关键字分割成位数相同的几部分,然后取这几部分的叠加和(去除进位)作为散列地址
  5. 随机数法:选择一随机函数,取关键字的随机值作为散列地址
  6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。对p的选择很重要,一般取素数或m

处理碰撞

  即使再好的哈希函数也可能会导致碰撞,因此我们必须合理的处理碰撞。处理碰撞主要有以下几种方法:
  1. 开放寻址法:Hi=(H(key) + di) MOD m,i=1,2,…,k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列,可有下列三种取法:
    1.1. di=1,2,3,…,m-1,称线性探测再散列;
    1.2. di=1^2,-1^2,2^2,-2^2,⑶^2,…,±(k)^2,(k<=m/2)称二次探测再散列;
    1.3. di=伪随机数序列,称伪随机探测再散列。
  2. 再散列法:Hi=RHi(key),i=1,2,…,k RHi均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。
  3. 链地址法(拉链法):也是最常用的方法,通过链表的形式将所有H(key)相同的元素链接到一起。查找时,首先找到映射后的散列地址H(key),然后再遍历对应的链表,从中找到对应key的元素。

哈希表的实现

  很多库都已经实现了hash表,如Java中HashMap的设计与实现http://blog.csdn.net/u012658346/article/details/50950492.
  C++的boost库中的unordered_set、unordered_map也都是基于Hash表的。
  而且这些hash表的实现基本都是基于数组+链表的,通过”拉链法“来处理碰撞的。

哈希表的应用

  由于hash表的查找效率为O(1),非常高,因此在很多时间复杂度要求较高的算法中都是利用HashMap、unordered_set、unordered_map来完成。
  同时现在很多的key-value数据库也都用到了hash表。
  一般在项目中,存在映射关系的数据都可以用hash表来实现

你可能感兴趣的:(哈希表)