散列函数(又称哈希函数)就是在符号表中将被查找的键转化为一个数组的索引,便于键值对的查询
一、正整数的散列函数
如果所有的键都是小整数,那么可以直接将键作为数组的一个索引。
如(1,A)、(2,B)、(3,C)
除留余数法
选择大小为素数M的数组,对于任意正整数k,计算k除以M的余数,将余数作为索引值。
例如一个60人的班级的同学们的学号与姓名作为键值对
张小芳 | 2131469 |
王钢蛋 | 2131587 |
刘大皮 | 2131694 |
李好 | 2131447 |
..... | ...... |
前两位21表示 2021年入学,第三四位31表示专业,后三位469表示编号。
直接将学号作为数组索引需要创建一个百万长度的数组去存60个人的信息过于浪费,取后三位作为索引会减少很多空间。但是长度1000的数组储存60条信息似乎还是浪费,这时可以用除留余数法。
选择大小为素数 M = 61 的数组,将每位同学学号后三位除以61得到余数如下
张小芳 | 42 |
王钢蛋 | 38 |
刘大皮 | 23 |
李好 | 20 |
... | ... |
取数组大小为素数 M = 61 是为了更好的分布分散,假如取 M = 100,369、469与569的余数都是69,而无法利用百位数字的信息,在选择散列函数时应尽可能地利用所有信息。当然取比60更大地素数会有更好地散列值分布,要平衡数组大小与散列值分布。
二、浮点数的散列函数
如果键是0-1之间的实数,可以将它乘以M并四舍五入得到一个(0, M-1)之间的一个整数作为索引值,但是这种情况下键的高位起的作用更大,最低位几乎没有影响。例如,M=997,两个键分别为0.10001、0.10002,通过上述方法,得到的结果都是100。最低位的1和2(加粗部分)对结果没有影响。
将浮点数的键表示为二进制数然后使用除留余数法便可消除这种影响。计算机中保存浮点数的标准是IEEE 754\854标准。IEEE 754标准详解。
以32位单精度浮点数举例
如计算-9.875的散列值
首先转换为二进制形式,-1001.111,科学计数法为-1.001111✖2³。
S(符号位):该值为负所以符号位S为1;
E(指数位):指数是3,单精度偏置值是127,所以指数位为130(转换为二进制是10000010);什么是偏置值?因为指数位有可能为负,而指数位E无法表达负数,所以设置一个偏置值(,单精度为127,双精度就为1023)加上指数便是完整的指数位。如 1.01✖
,指数为-8,仅靠二进制无法表达,但是有偏置值,使-8加上偏置值127等于119,119容易二进制表示1110111。偏置值相当于指数位有个初始化的值,避免其变为负数。
M(有效数字位):有效数字位指的是小数点后面的数字001111,由于其是32位单精度浮点数,所以我们要保证S+E+M有32位。S+E已经有了8位,所以M有24位,我们将有效数字001111后面加0补齐其余位数得到00111100000000000000000。最终得到一个32位的二进制整数(下图所示)11000001000111100000000000000000,转换为十进制3239968768。
3239968768即为-9.875的散列值,然后可以用除留余数法。
三、字符串的散列函数
在Java中使用UTF-16编码,UTF-16编码方式是Unicode编码方式之一(字符编码方式)。因为在UTF-16编码方式中,其基本平面占用2个字节,辅助平面占用4个字节,常见字符都是由2个字节组成,所以一个字符可以转化为一个16位整数,从而逐个提取字符将字符串转化为一个特有的数值。
Java中自带的函数如下
public static int javahash(String str) {
int hash = 0;
for (int i = 0; i < str.length(); i++) {
hash = hash * 31 + (int)str.charAt(i);
}
return hash & 0x7FFFFFFF;
}
四、组合键
如果键的类型含有多个整形变量,可以和String类型一样将它们混合起来。
如Date数据类型,包括day,month,year。可以用如下方式计算散列值
int hash = (((day*R+month)%M)*R+year) % M;
五、将hashCode()的值转化为一个数组索引
因为需要的是数组的索引而不是一个32位整数,现实中会将默认的hashCode()方法和除留余数法结合起来产生一个0到M-1的整数:
private int hash(Key x)
{
return (x.hashCode() & 0x7fffffff) % M;//M是素数
}
六、自定义hashCode
理想的hashCode方法是能够将键平均的分散到所有可能的32位整数,也就是说对于一个任意的对象x,x.hashCode() 可以有均等的机会获得
个不同整数任意一个数。Java中Integer、Double、String、URL、File对象的hashCode均能做到这一点。
例如定义一个数据类型Student
public class Student
{
...
private String name;
private int number;
private double gpa;
...
public int hashCode()
{
int hash = 17;
hash = hash*31 + name.hashCode();
hash = hash*31 + ((Integer)number).hashCode();//Java只有内置Integer类型的hashCode函
//数,因此int类型需要转化为包裹类型
hash = hash*31 + ((Double)gpa).hashCode();
return hash;
}
...
}
总结
散列函数的设置是个贼拉专业的技术,我不会。。