在前面讨论的各种结构(线性表、树等)中,记录在结构中的相对位置是随机的,和记录的关键字之后不存在确定的关系。因此,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立在比较的基础上。
理想的情况是希望不经过任何比较,一次存取就能得到所查记录。如何得到?
根据所查找的关键字计算元素位置,设法使得记录在表中的位置和它的关键字取值联系起来。
冲突:不同的关键字可能得到同一个哈希地址的现象,若可能出现冲突则应制定相应的冲突处理方法。如将同余的记录组成一个链表。
根据设定的哈希函数和处理冲突的方法,将一组关键字映像到一组有限连续的存储空间上,以关键字对应的Hash函数值作存储地址。如此所得表为哈希表。
映像过程称为哈希造表或散列。存储地址称为哈希地址或散列地址。
关键是构造合适的Hash函数和找合适的冲突处理方法。而好的Hash函数应该使冲突尽量少。
下面看一个实例:
使用Hash函数,实现对字符串S的哈希值的计算,n为字符串长度。哈希值计算过程中要注意取模,防止溢出。输入必须保证冲突处理所取的步长小于(1 << 31)。用二次探测再散列的方法处理冲突.
H(key) = (s[0] * 31^(n – 1) + s[1] * 31 ^ (n – 2) + ….. + s[n – 1] * 31 ^ 0) % mod;
可能用到的两个取模公式:
(a + b) % mod = (a % mod + b % mod) % mod
(a – b) % mod = (a % mod – b % mod + mod) % mod
输入:字符串的数目N,余数mod,各个字符串。
输出:对于各个字符串,输出计算得到的Hash地址。
运行结果:
具体思想:
按照公式计算,计算的时候小心溢出就可以了。
处理冲突:
开放定址法:处理冲突时加一个增量di。
H0 = H(key)。
Hi = (H0 + di) % m
直到Hs处无冲突。其中m为表长。
其中增量di的取值方法:二次探测再散列。
di=1,-1,4,-4,9, -9, 16, -16…
具体实现:
辅助宏:
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OVERFLOW -1
#define NULL 0
typedef int *SSTable;
typedef char* KeyType;
typedef int Status;
返回关键字key的哈希地址
int Hash(KeyType key,int mod,SSTable ST)
{
//返回关键字key的哈希地址
int i,l,j;
unsigned long long sum,hash=0;
l=strlen(key);
for(i=0;i<l;i++) //计算哈希地址
{
sum=1;
for(j=0;j<l-1-i;j++)
sum=(sum*(31%mod))%mod; //注意每次计算都要取余防止溢出
sum=(sum*(key[i]%mod))%mod;
hash=(hash+sum)%mod;
}
hash=hash%mod;
if(!ST[hash])
{
ST[hash]=TRUE;
return hash;
}
else //处理冲突
{
int i=1;
unsigned long long temp;
while(1)
{
temp=hash;
temp=( temp%mod+( (i%mod)*(i%mod) )%mod )%mod;
if(!ST[temp])
{
ST[temp]=TRUE;
break;
}
temp=hash;
temp=(temp%mod-((i%mod)*(i%mod))%mod+mod)%mod;
if(!ST[temp])
{
ST[temp]=TRUE;
break;
}
i++;
}
return temp;
}
}
哈希表查找的性能分析:
虽然哈希函数确定了关键字及其存储位置之间的映像,但由于冲突的存在,Hash表的查找仍然要进行比较,平均查找长度也并非一定为零。
决定哈希表查找的ASL的因素,哈希函数,处理冲突方法以及哈希表的装填因子(饱和度)a=n/m n-记录数 m-表的长度。
通常假定哈希函数均匀。从而忽略其对ASL的影响。平均情况下,不同的冲突处理办法下ASL是装填因子的一个函数。而不是n的函数。不管n多大,总可选择合适的装填因子时ASL限定在一个范围内。