散列表
目的 :如果所有的键都是小整数,可以用一个数组 来实现无序的符号表,将键 作为数组的索引而数组中键i
处存储的就是它对应的值。查找算法步骤
用散列函数 将查找的函数转化为数组的一个索引。理想情况下,不同的减可以转换为不同的索引值。 处理碰撞冲突 ,分为拉链法 和线性探测法 。散列表是算法在时间和空间上的均衡处理方式。
散列函数
散列函数特点 :散列函数应该易于计算 并且能够均匀分布 所有的键,即对于任意的键,所给出的散列值应得呈随机性。散列函数与键的类型有关。
正整数 :正整数散列最常用的方式为除留余数法 。选择一个M
的素数的数组,对于任意正整数k
,计算k%M
。如果不是素数,可能无法获得均匀的散列值。浮点数 :如果键值是0到1之间,可以将键值乘以素数M后,四舍五入得到一个0到M-1之间的索引值。但这样会使键的高位起作用更大,低位键值不影响散列值。解决办法是,键值用二进制表示后使用除留余数法 。字符串 :字符串散列同样使用除留余数法 ,将字符串考虑为大整数即可。Java的charAt()
函数能够返回一个非负16位整数。如果R是比任何字符都大的整数,相当于,将字符串当做一个N 位的R 进制值,将它除以M求余。Horner方法,用N次乘法、加法和取余来计算一个字符串的散列值。只要R足够小,不造成溢出。 java int hash = 0; for (int i = 0; i < s.length(); i++) hash = (R * hash + s.charAt(i)) % M
组合键 :如果键的类型包含多个整形变量,我们可以和String类型一样将他们混合起来。例如,被查找的类型是Date,其中整型域:day(两位数)、month(两位数)和year(四位数),散列值是: Java int hash = (((day * R + month) % M) * R + year) % M;
只要R足够小不造成溢出,也可以得到0到M-1之间的散列值。同时选取适当素数M值,例如31,来省去括号内的%M
计算。 Java约定 :即散列值的硬性规则。由于每种数据类型都需要相应的散列函数,于是Java让所有数据类型都继承了一个能够返回32bit整数 的hashCode()方法。因此若a.equals(b)
的值为ture
,那么对应的hashCode值也应相同,若hashCode值不同,只能说明看似相同的数据,实则为不同数据类型。但同hashCode值,两个对象可能不同。将hashCode的返回值转化为数组索引 :若我们想使用短的索引值,而不是32bit整数的散列值,这里利用hashCode()
方法和除留余数法 结合,产生0到M-1的整数。
private int hash (Key x){
return (x.hashCode() & 0x7ffffffff ) % M;
}
自定义的hashCode()方法 :可以按照将hashCode的返回值转化为短整数的方法,进行变换。将对象的每个变量的hashCode()返回值转化为32位整数并计算得到散列值。
public class Transaction {
...
private final String who;
private final Date when;
private final double amount;
public int hashCode (){
int hash = 17 ;
hash = 31 * hash + who.hashCode();
hash = 31 * hash + when.hashCode();
hash = 31 * hash + ((Double) amount).hashCode();
return hash;
}
...
}
软缓存: 如果散列值的计算很耗时,可以将计算的散列值存储在每个键的散列值函数里,即利用hash变量存储hashCode()的返回值。
基于拉链法的散列表
基于线性探测法的散列表