文章目录
- 1.哈希函数的定义
- 2.哈希表
- 3.好的哈希函数特点
- 4.常见的哈希函数的构造方法
- 6.字符串的哈希查找
1.哈希函数的定义
- 一般情况下,需在关键字与记录在表中的存储位置之间建立一个函数关系,以 H(key) 作为关键字为key 的记录在表中的位置,通常称这个函数 h(key) 为哈希函数。
- 哈希函数的特点
(1)哈希函数是一个映象,即: 将关键字的集合映射到某个地址集合上, 它的设置很灵活,只要这个地址集合的大小不超出允许范围即可;
(2) 由于哈希函数是一个压缩映象,因此,在一般情况下,很容易产生“冲突” 现象,即: key1≠key2,而 h(key1) = h(key2)。
- 很难找到一个不产生冲突的哈希函数。
一般情况下, 只能选择恰当的哈希函数,使冲突尽可能少地产生。
因此,哈希查找需要做两方面事情:选择一个“好”的哈希函数;提供一种“处理冲突” 的方法。
- eg:
在学号范围内:XX000 ~ XX999,查找学号:17138,选择查找的方法
(1)顺序查找: O(n),平均约比较500次(平均比较的次数为:n/2)
(2)二分查找: O(logn),平均约比较10次
(3)哈希查找:取给定学号的后三位, 不需要经过比较, 便可直接从查找表中找到给定学生的记录。
2.哈希表
- 根据设定的哈希函数 H(key) 和提供的处理冲突的方法,将一组关键字映象到一个地址连续的地址空间上,并以关键字在地址空间中的“象” 作为相应记录在表中的存储位置,如此构造所得的查找表称之为哈希表。
地址空间存储的数据集合称为哈希表
3.好的哈希函数特点
- (1)计算简单
- (2)冲突少
- 实际工作中需根据不同的情况采用不同的哈希函数。通常需要考虑的因素有:
计算哈希函数所需时间;
关键字的长度;
哈希表的大小;
关键字的分布情况;
记录的查找频率
4.常见的哈希函数的构造方法
- 直接哈希函数
取关键字本身或关键字的某个线性函数值作为哈希地址,
即: H(key) =key
或 H(key) =a* key+b(a, b为常数)。
eg:key是出生年份
- 数字分析法
设n个d位数的关键字,由r个不同的符号组成,此r个符号在关键字各位出现的频率不一定相同,可能在某些位上均匀分布,即每个符号出现的次数都接近于n/r次,而在另一些位上分布不均匀。则选择其中分布均匀的s位作为哈希地址,即H(key) =“key中数字均匀分布的s位”。
eg:
对于第一位和第八位而言,第一位只有8,第八位只有2,7,8不满足每个符号出现的次数接近于n/r
- 平均取中法
取关键字平方后的中间几位作为哈希地址,即哈希函数为:
H(key) =“key的平方的中间几位”,
其中,所取的位数由哈希表的大小确定
以关键字的平方值的中间几位作为存储地址。求“关键字的平方值” 的目的是“扩大差别”和“贡献均衡”。
即:关键字的各位都在平方值的中间几位有所贡献, Hash值中应该有各位影子。
eg:请为BASIC源程序中的标识符建立一个哈希表。假设BASIC语言中允许的标识符为一个字母,或一个字母加一个汉字。
取标识符在计算机中的八进制数为它的关键字。
- 折叠法(若关键字位数特别多)
(1)关键字位数较长时,可将关键字分割成位数相等的几部分(最后一部分位数可以不同),取这几部分的叠加和(舍去高位的进位)作为哈希地址。位数由存储地址的位数确定。
(2)叠加时有两种方法:
• 移位叠加法,即将每部分的最后一位对齐,然后相加;
• 边界叠加法,即把关键字看作一纸条,从一端向另一端沿边界逐次折叠,后对齐相加。
eg:此种方法适合于:关键字的数字位数特别多的情况 - 除留余数法(若关键字位数特别多)
取关键字被某个不大于哈希表长度m的数p除后的余数作为哈希地址,即:
H(key) =key MOD p(p≤m)
p通常要选择质数
eg:
- 随机数法(若关键字位数特别多)
选择一个随机函数,取关键字的随机函数值作为哈希地址,即:
H(key) =random(key)
其中random为随机函数。
6.字符串的哈希查找
- eg:牛津字典的查找和编译器的符号表都是哈希查找
Example〗 In Oxford English dictionary
name = since
attribute = a list of meanings:M[0] = after a date, event, etc.
M[1] = seeing that (expressing reason)
…… ……
- 对于哈希函数的要求
(1) f ( x )必须能够计算任意关键字且冲突最少
(2) f ( x ) 应该均匀分布
比如对任意 x和 i, 有如下概率:
Probability( f ( x ) = i ) = 1 / b. 这种函数函数叫(均匀分布哈希函数)
uniform hash function.
f ( x ) = x % TableSize ; /* if x is an integer */
说明:
因为:
如果TableSiz=10,如果x都是10的倍数的整数,其哈希值全都为0,会有大量的冲突
所以:
TableSize=prime number,即:TableSize的取值为选择小于TableSize的最大质数
- 如何获取字符串的x?也就是说,如何获取字符串的key x?
由于每个字符的ASCII码本身就是整数,利用ASCII码进行求和来作为x,所以哈希函数为:
f ( x ) = (Σ x[i]) % TableSize ; /* if x is a string */
eg:TableSize = 10,007 and string length of x <= 8(字符串的长度).
If x[i] ∈ [0, 127](每个字符的ASCII只能是0-127), then f(x)∈ [0, 1016]
(哈希值的结果最多是:128*8=1016种情况)
因为这种情况哈希值的范围最多为1016,远远小于10007,所以需要扩大key值。
扩大key的方法是:26个字符+空格=27个,取前三位
f(x) = (x[0]+ x[1]*27 + x[2]*27^2) % TableSize;
相当于:*27^0 *27^1 *27^2
因为:
128*27^2已经超过10007了,避免了前面只有f(x)只有1016的大小,但是所有字符串的前3个字符的组合
是少于3000个的,
所以:
只取部分位又会出现重复,此外,求幂运算的计算量很大
结论:
要让所有的位都参与到哈希函数的计算当中,高次幂转换成移位操作,这样计算速度就很快,公式如下(乘以32相当于左移5位):
说明:
(1)若字符串很长,可以让整个字符串参与整体移位,需要自己写移位操作函数
(2)若字符串很长,可以选择某些位来参与运算
(2)的代码为:
//x是字符串
Index Hash3(const char *x, int TableSize)
{
unsigned int HsahVal=0;
while(*x!='\0')
{
//如果x太长,选择x当中的某些字符来实现哈希操作,以防止左移出空间之外
HashVal=(HashVal<<5)+(*x)++;
}
return HashVal%TableSize;
}