散列表的定义

在查找数据对象时,由函数h对给定值key计算出地址,将key与该地址单元中数据对象关键字进行比较,确定查找是否成功。因此,散列法又称为“关键字-地址转换法”。散列方法中使用的计算函数称为散列函数(也称哈希函数),按这个思想构造的表称为散列表,所以它是一种存储方法。

装填因子

一般情况下,设散列表空间大小为m,填入表中的元素个数是n,则称α=n/m为散列表的装填因子,例如大小为17,元素为11,装填因子为0.65.实用时,常见散列表大小设计使得α=0.5~0.8为宜。

同义词

映射到同一散列地址上的关键字称为同义词

散列函数的构造方法

一个“好”的散列函数一般应考虑下列两个因素:

  • 计算简单,以便提高转换速度
  • 关键词对应的地址空间分布均匀,以尽量减少冲突。即对于关键词集合中的任何一个关键字,经散列函数映射到地址集合中任何一个地址的概率是基本相等的。

数字关键词的散列构造

直接定址法

java散列表的系统 散列表的目的是_散列函数


这类函数计算简单,分布均匀,不会产生冲突,但要求地址集合与关键词集合大小相同,因此,对于较大的关键词集合不适用。所以在现实应用中并不常用

除留取余法

现实应用中比较常用的方法是除留取余法。假设散列表长为TableSize(TableSize的选取,通常由关键词集合的大小n和允许最大装填因子α决定,一般将TableSize取为n/α),选择一个正整数p<=TableSize,散列函数为:

h(key)=key mod p

即取关键词除以p的余数作为散列地址。使用除留取余法,选取合适的p很重要,一般选取p为小于或等于散列表长TableSize的某个最大素数比较好。用素数求得的余数作为散列地址,比较均匀分布在整个地址空间上的可能性较大。

java散列表的系统 散列表的目的是_字符串_02

数字分析法

java散列表的系统 散列表的目的是_java散列表的系统_03


这种方法的核心就是利用数字的随即部分产生散列值

字符串关键词的散列函数构造

对于字符串类型的关键词,因为字符串的比较比整数的比较要花费更大的代价,所以通过散列函数计算,把字符串映射到整数后再比较也是散列方法的优势之一。

ASCII码加和法

java散列表的系统 散列表的目的是_散列函数_04

这种的方法核心是将ascii码字符与散列表长取mod,冲突比较明显,比如:tea与eat 单词换换位置,散列值却相同。

前3个字符移位法

java散列表的系统 散列表的目的是_散列函数_05


若忽略空格符不计,则前3位所有可能的不同组合有26^3=17 576种,似乎TableSize=10 007是个不错的选择,但是英文单词也是有规则,最多3 000种变换,装填因子过小,也是浪费。因此,虽然很容易计算,但是当散列表太大的时候,这个函数还是不合适的。

移位法

java散列表的系统 散列表的目的是_散列函数_06

这种方法将所有的字符进行移位做散列,很容易产生前几位溢出 。一般用这种方法时,是不使用整个字符串,而是从中选择若干有代表性的字符进行映射。 比如字符串长度大小大于12的时候,仅选取奇数位置上的字符来实现散列函数。

处理冲突的方法

开放定址法

java散列表的系统 散列表的目的是_散列表_07


而di的选取又有如下几种方案

线性探测

java散列表的系统 散列表的目的是_散列函数_08


java散列表的系统 散列表的目的是_散列表_09


在做插入的时候,最尴尬的就是30,与11取模等于8,也就是把这个位置,因为29存的是时候与7产生冲突,所以存在本应30村的地方,后面不断发生冲突,最后30只能跑到1中。真的非常扎心,这种线性探测会带来一定的麻烦。可能会出现很多元素在相邻的散列地址上“堆积”起来的现象,会大大降低查找效率。 解决这种问题的方法是,可采用其他探测法。

平方探测法

java散列表的系统 散列表的目的是_字符串_10


java散列表的系统 散列表的目的是_字符串_11


java散列表的系统 散列表的目的是_java散列表的系统_12


使用平方探测,研究学者已经给出一个强大的理论保证,也就是:如果散列表长度TableSize是某个4k+3(k是正整数)形式的素数时,平方探测法就可以探查到整个散列表空间。

大家不要小看这个结论,我们在前面学过最多的是遍历,遍历整一个是我们理想情况,平方探测能做到,说明两个字:用它!

双散列探测法

java散列表的系统 散列表的目的是_散列表_13


java散列表的系统 散列表的目的是_散列函数_14


采用双散列探测法会增加每次探测的乘法和除法运算,但其期望的探测次数比较少,这使得它在理论上很有吸引力。

再散列法

英文名(Rehashing),这个我好像在侯捷老师stl立面讲到过,这个方法就是解决装填因子过大的情况,解决的方法是加倍扩大散列表,这样α可以减小一半,这个过程叫做再散列。这种需要扩充可能会出现实时系统的停顿,因此如果一定要实时那就需要“分离链接法”

分离链接法

java散列表的系统 散列表的目的是_字符串_15


建立m个带头结点的空链表,散列地址位i的所有关键词均插入到Head[i]指向的单链表中。插入时,新元素插入到表头,这不仅为了放便,而且还因为新近插入的元素最有可能被访问,这样可以加快在单链表的顺序查找速度。

散列表的性能分析

散列表主要涉及在查找上面,避免冲突是他要解决的,衡量性能也从这里分析,影响产生冲突多少有以下三个因素:

  • 散列函数是否均匀
  • 处理冲突的方法
  • 散列表的装填因子α

线性探测法的查找性能

java散列表的系统 散列表的目的是_字符串_16

平方探测法和双散列探测法的查找性能

java散列表的系统 散列表的目的是_java散列表的系统_17

分离链接法查找性能

java散列表的系统 散列表的目的是_散列函数_18

期望探测次数与装填因子α的关系

java散列表的系统 散列表的目的是_散列表_19


当装填因子α<0.5的时候,各种探测法的期望探测次数都不大,也比较接近。

随着α的增大,线性探测法的期望探测次数增加较快,不成功查找和插入查找的期望探测次数比成功的期望探测次数要大。因此合理的最大装入因子α应该不超过0.85