散列函数的构造方法
数字关键词的散列函数构造
- 直接定址法
h(key) = a * key + b; //a, b为常数
int h(int key)
{
int a = 1, b = 2;
return a * key + b;
}
这类函数计算简单,分布均匀,不会产生冲突,但要求地址集合与关键字集合大小相同,因此,对于较大的关键词集合不适用。所以在现实中并不常用
- 除留余数法
假设散列表长度为TableSize,选取一个正整数p <= TableSize
h(key) = key mod p;
int h(int key, int p)
{
return key % p;
}
注意到,如果p < TableSize,则意味着地址p ~ TableSize - 1是不能通过散列表直接映射到的。不过不用担心空间浪费,在冲突发生时就会用到它们
- 数字分析法
如果数字关键词的位数比较多,在特定的情况下,有些位数易相同,而有些位数比较随机。比如11位手机号码,前3位容易相同,中间4位表示用户归属地,在一定范围内也容易重复,而最后4位是很随机的。所以只选择后4位作为散列地址,采用字符串存储号码,C语言中的字符串转换成整数处理函数atoi,于是散列函数表示为:
h(key) = atoi(key + 7) mod p; //如果4位数还是太大了, 再用一次取模
这里key + 7表示指针往后移7个数字,即留下4位数字
int h(char key[], int p)
{
return atoi(key + 7) % p;
}
字符串关键词的散列函数构造
- 一个简单的散列函数——ASCII码加和法
h(key) = (∑key[i]) mod TableSize;
显然冲突可能非常严重,比如a3, b2, c1的散列值都是100;关键词"tea"和"eat"也是冲突的
- 简单的改进——前3个字符移位法
h(key) = (key[0] + key[1] * 27 + key[0] * 27^2) mod MaxSize;
选择27进制是因为26个字母和一个字符空格,该散列函数只考虑前3个字符,当散列表太大时,这个函数还是不合适的
- 好的散列函数——移位法
这个散列函数涉及关键词的所有n个字符,并且分布的很好
该函数用于处理长度为n的字符串关键词,每位字符占5位(即2^5 = 32)。具体实现时并不需要做乘法运算,而是通过一次左移5位来完成,这也是为什么选用32来代替27的原因
int h(const char *key, int TableSize)
{
int h = 0;
while(*key != '\0')
h = (h << 5) + *key++;
return h % TableSize;
}
该函数遇到的主要问题是,当n太大时,前面若干位字符可能被左移出界,而起作用的只有最后几位字符。一种解约的办法是不使用整个字符串,而是从中选择若干位有代表性的字符进行映射,比如字符串长度大于12的时候,仅选取奇数位置上的字符来实现散列函数