数据结构 Hash表(哈希表)

282 阅读5分钟

一、什么是Hash表

要想知道什么是哈希表,那得先了解哈希函数 哈希函数 对比之前博客讨论的二叉排序树 二叉平衡树 红黑树 B B+树,它们的查找都是先从根节点进行查找,从节点取出数据或索引与查找值进行比较。那么,有没有一种函数H,根据这个函数和查找关键字key,可以直接确定查找值所在位置,而不需要一个个比较。这样就**“预先知道”** key所在的位置,直接找到数据,提升效率。即地址index=H(key)说白了,hash函数就是根据key计算出应该存储地址的位置,而哈希表是基于哈希函数建立的一种查找表。

二、哈希函数的构造方法

根据前人经验,统计出如下几种常用hash函数的构造方法:

直接定制法

哈希函数为关键字的线性函数如 H(key)=a*key+b 这种构造方法比较简便,均匀,但是有很大限制,仅限于地址大小=关键字集合的情况 使用举例: 假设需要统计中国人口的年龄分布,以10为最小单元。今年是2018年,那么10岁以内的分布在2008-2018,20岁以内的分布在1998-2008……假设2018代表2018-2008直接的数据,那么关键字应该是2018,2008,1998…… 那么可以构造哈希函数H(key)=(2018-key)/10=201-key/10 那么hash表建立如下:

indexkey年龄人数(假设数据)
020180-10200W
1200810-20250W
2199820-30253W
3198830-40300W
……

数字分析法

假设关键字集合中的每个关键字key都是由s位数字组成(k1, k2, ……, kn), 分析key中的全体数据,并从中提取分布均匀的若干位或他们的组合构成全体

使用举例

  • 我们知道身份证号是有规律的,现在我们要存储一个班级学生的身份证号码,假设这个班级的学生都出生在同一个地区,同一年,那么他们的身份证的前面数位都是相同的,那么我们可以截取后面不同的几位存储,假设有5位不同,那么就用这五位代表地址。
  • H(key)=key%100000
  • 此种方法通常用于数字位数较长的情况,必须数字存在一定规律,其必须知道数字的分布情况,比如上面的例子,我们事先知道这个班级的学生出生在同一年,同一个地区。

平方取中法

如果关键字的每一位都有某些数字重复出现频率很高的现象,可以先求关键字的平方值,通过平方扩大差异,而后取中间数位作为最终存储地址。

使用举例

  • 比如key=1234 1234^2=1522756 取227作hash地址
  • 比如key=4321 4321^2=18671041 取671作hash地址 这种方法适合事先不知道数据并且数据长度较小的情况

折叠法

如果数字的位数很多,可以将数字分割为几个部分,取他们的叠加和作为hash地址

使用举例

  • 比如key=123 456 789
  • 我们可以存储在61524,取末三位,存在524的位置
  • 该方法适用于数字位数较多且事先不知道数据分布的情况

除留余数法

此方法用的较多 H(key)=key MOD p (p<=m m为表长)很明显,如何选取p是个关键问题。

使用举例

  • 比如我们存储3 6 9,那么p就不能取3
  • 因为 3 MOD 3 == 6 MOD 3 == 9 MOD 3
  • p应为不大于m的质数或是不含20以下的质因子的合数,这样可以减少地址的重复(冲突)
  • 比如key = 7,39,18,24,33,21时取表长m为9 p为7 那么存储如下: index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | ----- | - | -------- | - | -- | ---- | -------- | ------- | - | - | | key | 7 | 21(冲突后移) | | 24 | 39 | 18(冲突后移) | 33冲突后移)

随机数法

H(key)=Random(key)取关键字的随机函数值为它的散列地址

hash函数设计的考虑因素

  1. 计算散列地址所需要的时间(即hash函数本身不要太复杂)
  2. 关键字的长度
  3. 表长
  4. 关键字分布是否均匀,是否有规律可循
  5. 设计的hash函数在满足以上条件的情况下尽量减少冲突

三、哈希冲突

  • 即不同key值产生相同的地址,H(key1)=H(key2)
  • 比如我们上面说的存储3 6 9,p取3是
  • 3 MOD 3 == 6 MOD 3 == 9 MOD 3
  • 此时3 6 9都发生了hash冲突

哈希冲突的解决方案

不管hash函数设计的如何巧妙,总会有特殊的key导致hash冲突,特别是对动态查找表来说。 hash函数解决冲突的方法有以下几个常用的方法:

  1. 开放定制法
  2. 链地址法
  3. 公共溢出区法 建立一个特殊存储空间,专门存放冲突的数据。此种方法适用于数据和冲突较少的情况。
  4. 再散列法 准备若干个hash函数,如果使用第一个hash函数发生了冲突,就使用第二个hash函数,第二个也冲突,使用第三个…… 重点了解一下开放定制法和链地址法。