数据结构 哈希表学习

哈希表(基于数组):

插入利用哈希算法计算出要插入的数据项对应的下标的值,如果发生冲突此数组元素已有值,可采用开放地址法(插入到下一个空值的地方)或链地址法(新的数据项插入到数组下标所指的链表中)。

开放地址法的删除会让要删除的数组元素等于特定的某个数据项,而不是直接等于null,可以使查找的方法顺利进行。

 

填装因子:

在开放地址法的线性探测中插入数据会发生聚集(哈希值相同的数据项冲突所产生的聚集),聚集特别是大量的聚集会影响效率。已填入哈希表中的数据项和表长的比率叫做填装因子(如2/3)。

 

扩展数组:

扩展后的数组容量通常是原来的两倍,数组容量应该是一直质数(质数可保证分布均匀),所以新数组要比两倍的容量多一点。扩容后要是使用insert方法重新把旧数组中的数据插入到新数组中,这个过程叫做重新哈希化。

 

开放地址法的三种方式:线性探测,二次探测,再哈希化

 

线性探测:当插入放生冲突时,寻找下一个可插入的空白单元插入。查找也是基于此。

 

二次探测(不常用):

插入时使用线性探测会产生聚集,二次探测是线性探测的替代方法。二次探测的步数是线性探测步数的平方,如哈希函数计算的原始下标是x,线性探测是x+1,x=2,x+3以此类推,那二次探测过程是x+1,x+4,x+9....,但是用二次探测代替线性探测进行插入也有可能产生二次聚集。

 

再哈希化:

二次聚集的原因是二次探测的步长是固定的(1,4,9...)。在哈希化是把关键字用不同的哈希函数在做一遍哈希化,用这个结果作为步长。

第二个哈希函数应具备两个特点:1.和第一个函数不同 2.不能输出0(步长不能为0),如比较好用的一种:stepSize= constant-(key%constant),constant是质素,且小于数组容量

再哈希化要求哈希表的容量是一个质数,若不为质数用再哈希化可能会一直循环数组中的部分元素,而不能找到目标的空元素。

使用开放地址策略时,探测序列通常用再哈希法生成。

 

链地址法:

在哈希表每个单元中设置链表,某个数据项的关键字值还是想通常一些样映射到哈希表的单元,而数据项本身插入到这个单元的链表中。

存取效率,采用链地址法的哈希表存取效率是O(M),M是链表包含的平均项数

填装因子,链地址法的哈希表包含的数据项可以比单元的数量要打,所以装填因子一般为1,或比1大。当为1时,大约三分之一时空白单元,三分之一有一个数据项,三分之一有两个或多个数据项。

删除,可以允许重复值,但会使性能略微下降

删除,数据项会直接删除,不采用软删除的方式。

表的容量,值为质数不那么重要,但不是质数还是会有聚集的问题。

桶,链地址法的变形,每个单元对应的是数组而不是链表,这样的数组有时被称为桶。但效率会比链地址放效率低(数组的容量固定的原因)。

外部存储,当数据项存储采用外部存储时,哈希表中的数组相当于索引,由此这时的哈希表也被叫做索引。

 

哈希函数:

 

快速计算:

好的哈希函数简单快速。不建议使用过多的乘法和除法。

 

随机关键字:

完美的哈希函数可以吧每个关键字都映射到表中的不同位置,一般这只存在于关键字异乎寻常的好,且数组范围足够,但这种情况很少发生。大多数情况下,哈希函数需要把较大的关键字范围压缩成较小的数组下标的范围。在随机关键字中,哈希函数index=key%arraySize就会有良好的分布情况

 

非随机关键字

 

通常数据不会是随机分布的,为了确保结果靠近随机数分布的范围,需要:

  1. 不要使用无用数据(关键字中包含的无意义的部分,或者实际不可能存在的关键字)
  2. 使用所有的数据(除了无用数据的部分)
  3. 使用质数作为取模的基数

即要去掉关键字中的无用数据,尽可能使用所有有用数据,并使用质数作为取模的基数。

 

哈希化字符串

英文字母加空格总数是27,比如cats:Key=3*27^3+1*27^2+20*27^1+19*27^0,这种方式能包含所有字符串的所有可能。Java代码如下:

Int hashFunc(String key){

int hashVal = 0;

for(int j = 0;j<key.length(j);j++){

Int letter = key.charAt(j) -96; //获取字母code值

hashVal = (hashVal*27+letter)%arraySize;
}

return hashVal;

}

折叠,另一个常见的哈希函数是把关键字中的数字分成几组,然后几个组相加,如号码是123-45-6789,可以按照公式123+45+789,再用取模操作获取对应的数组下标值。

 

 

哈希化的效率:

在没有冲突的情况下,哈希的插入和搜索效率可以达到O(1)级。如发生冲突,每个单元的存取时间要加上寻找一个空白单元或一个已存在单元的时间。而这个寻找的时间与探测长度成正比,平均探测长度取决于填装因子,填装因子越大,探测长度也越长。

开放地址法,探测序列P和填装因子L的关系,成功查找是:P=(1+1/(1-L)^2)/2,不成功的查找是:P=(1+1/(1-L)^2)/2,所以填装因子保持在2/3以下,最好在1/2以下。当填装因子为1/2时,成功搜索需要1.5次比较,不成功需要5次;填装因子为2/3时,陈宫需要2次,不成功需要5次。但另一方面,对于给定数量的数据项,随着填装因子变小,存储效率下降,而速度上升。

链地址法,由于填装因子L=N/arraySiz,是由于成功查找是:1+L/2,不成功查找是:1+L,插入:1+L/2

开放地址法和链表地址法的比较,小型的哈希表,开放地址法更容易实现。在哈希表创建时要填入的数据项的项数未知,链地址法要好于开放地址法,一般当二者都可选时,使用链地址法。

 

哈希化和外部存储:

哈希表的数据项采用外部存储策略,这时哈希表的单元会指向外部存储器中的块,这时哈希表就被叫做索引。

所有关键字映射为同一个值得记录都定位到相同块。搜索算法哈希化关键字,用哈希值作为哈希表的下标,得到某个下标中的块号,然后读取这个块。因为设计时块中的空间不能被填满,相当多的磁盘空间会被浪费掉。因此这个方案必须谨慎选择哈希函数和哈希表的大小,为的是限制映射到相同的值得关键字的数量。

即使一个好的哈希函数,块偶尔也会被填满,这时可以在内部哈希表中使用开放地址法或链地址法。

哈希表(基于数组):

插入利用哈希算法计算出要插入的数据项对应的下标的值,如果发生冲突此数组元素已有值,可采用开放地址法(插入到下一个空值的地方)或链地址法(新的数据项插入到数组下标所指的链表中)。

开放地址法的删除会让要删除的数组元素等于特定的某个数据项,而不是直接等于null,可以使查找的方法顺利进行。

 

填装因子:

在开放地址法的线性探测中插入数据会发生聚集(哈希值相同的数据项冲突所产生的聚集),聚集特别是大量的聚集会影响效率。已填入哈希表中的数据项和表长的比率叫做填装因子(如2/3)。

 

扩展数组:

扩展后的数组容量通常是原来的两倍,数组容量应该是一直质数(质数可保证分布均匀),所以新数组要比两倍的容量多一点。扩容后要是使用insert方法重新把旧数组中的数据插入到新数组中,这个过程叫做重新哈希化。

 

开放地址法的三种方式:线性探测,二次探测,再哈希化

 

线性探测:当插入放生冲突时,寻找下一个可插入的空白单元插入。查找也是基于此。

 

二次探测(不常用):

插入时使用线性探测会产生聚集,二次探测是线性探测的替代方法。二次探测的步数是线性探测步数的平方,如哈希函数计算的原始下标是x,线性探测是x+1,x=2,x+3以此类推,那二次探测过程是x+1,x+4,x+9....,但是用二次探测代替线性探测进行插入也有可能产生二次聚集。

 

再哈希化:

二次聚集的原因是二次探测的步长是固定的(1,4,9...)。在哈希化是把关键字用不同的哈希函数在做一遍哈希化,用这个结果作为步长。

第二个哈希函数应具备两个特点:1.和第一个函数不同 2.不能输出0(步长不能为0),如比较好用的一种:stepSize= constant-(key%constant),constant是质素,且小于数组容量

再哈希化要求哈希表的容量是一个质数,若不为质数用再哈希化可能会一直循环数组中的部分元素,而不能找到目标的空元素。

使用开放地址策略时,探测序列通常用再哈希法生成。

 

链地址法:

在哈希表每个单元中设置链表,某个数据项的关键字值还是想通常一些样映射到哈希表的单元,而数据项本身插入到这个单元的链表中。

存取效率,采用链地址法的哈希表存取效率是O(M),M是链表包含的平均项数

填装因子,链地址法的哈希表包含的数据项可以比单元的数量要打,所以装填因子一般为1,或比1大。当为1时,大约三分之一时空白单元,三分之一有一个数据项,三分之一有两个或多个数据项。

删除,可以允许重复值,但会使性能略微下降

删除,数据项会直接删除,不采用软删除的方式。

表的容量,值为质数不那么重要,但不是质数还是会有聚集的问题。

桶,链地址法的变形,每个单元对应的是数组而不是链表,这样的数组有时被称为桶。但效率会比链地址放效率低(数组的容量固定的原因)。

外部存储,当数据项存储采用外部存储时,哈希表中的数组相当于索引,由此这时的哈希表也被叫做索引。

 

哈希函数:

 

快速计算:

好的哈希函数简单快速。不建议使用过多的乘法和除法。

 

随机关键字:

完美的哈希函数可以吧每个关键字都映射到表中的不同位置,一般这只存在于关键字异乎寻常的好,且数组范围足够,但这种情况很少发生。大多数情况下,哈希函数需要把较大的关键字范围压缩成较小的数组下标的范围。在随机关键字中,哈希函数index=key%arraySize就会有良好的分布情况

 

非随机关键字

 

通常数据不会是随机分布的,为了确保结果靠近随机数分布的范围,需要:

  1. 不要使用无用数据(关键字中包含的无意义的部分,或者实际不可能存在的关键字)
  2. 使用所有的数据(除了无用数据的部分)
  3. 使用质数作为取模的基数

即要去掉关键字中的无用数据,尽可能使用所有有用数据,并使用质数作为取模的基数。

 

哈希化字符串

英文字母加空格总数是27,比如cats:Key=3*27^3+1*27^2+20*27^1+19*27^0,这种方式能包含所有字符串的所有可能。Java代码如下:

Int hashFunc(String key){

int hashVal = 0;

for(int j = 0;j<key.length(j);j++){

Int letter = key.charAt(j) -96; //获取字母code值

hashVal = (hashVal*27+letter)%arraySize;
}

return hashVal;

}

折叠,另一个常见的哈希函数是把关键字中的数字分成几组,然后几个组相加,如号码是123-45-6789,可以按照公式123+45+789,再用取模操作获取对应的数组下标值。

 

 

哈希化的效率:

在没有冲突的情况下,哈希的插入和搜索效率可以达到O(1)级。如发生冲突,每个单元的存取时间要加上寻找一个空白单元或一个已存在单元的时间。而这个寻找的时间与探测长度成正比,平均探测长度取决于填装因子,填装因子越大,探测长度也越长。

开放地址法,探测序列P和填装因子L的关系,成功查找是:P=(1+1/(1-L)^2)/2,不成功的查找是:P=(1+1/(1-L)^2)/2,所以填装因子保持在2/3以下,最好在1/2以下。当填装因子为1/2时,成功搜索需要1.5次比较,不成功需要5次;填装因子为2/3时,陈宫需要2次,不成功需要5次。但另一方面,对于给定数量的数据项,随着填装因子变小,存储效率下降,而速度上升。

链地址法,由于填装因子L=N/arraySiz,是由于成功查找是:1+L/2,不成功查找是:1+L,插入:1+L/2

开放地址法和链表地址法的比较,小型的哈希表,开放地址法更容易实现。在哈希表创建时要填入的数据项的项数未知,链地址法要好于开放地址法,一般当二者都可选时,使用链地址法。

 

哈希化和外部存储:

哈希表的数据项采用外部存储策略,这时哈希表的单元会指向外部存储器中的块,这时哈希表就被叫做索引。

所有关键字映射为同一个值得记录都定位到相同块。搜索算法哈希化关键字,用哈希值作为哈希表的下标,得到某个下标中的块号,然后读取这个块。因为设计时块中的空间不能被填满,相当多的磁盘空间会被浪费掉。因此这个方案必须谨慎选择哈希函数和哈希表的大小,为的是限制映射到相同的值得关键字的数量。

即使一个好的哈希函数,块偶尔也会被填满,这时可以在内部哈希表中使用开放地址法或链地址法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值