一、哈希表是什么?
从上面两节讨论的表示查找表的各种结构,有一个共同点:记录在表中的位置和它的关键字之间不存在一个确定的关系,因此,查找的过程为给定值依次和关键字集合中各个关键字进行比较,查找的效率取决于和给定值进行比较的关键字的个数。
用这类方法表示的查找表,其平均查找长度都不为零,不同表示方法的差别仅在于:和给定值进行比较的关键字的顺序不同。
对于频繁使用的查找表,希望ASL=0。只有一个办法:预先知道所查关键字在表中的位置,也就是说,记录在表中位置和其关键字之间存在一种确定的关系。
例如:
为每年招收的1000名新生建立一张查找表,其关键字xx000~xx999(前两位为年份)。则可以下标为000~999的顺序表表示之。由于关键字和记录在表中的序号相同,则不需要经过比较即可确定待查关键字。
但是,对于动态查找表而言,
1) 表长不确定;
2) 在设计查找表时,只知道关键字所属范围,而不知道确切的关键字。
因此,一般情况,需建立一个函数关系,以f(key)作为关键字为key的记录在表中的位置,通常称这个函数f(key)为哈希函数。
简单地说,哈希表是基于哈希函数建立的一种查找表。
例如:对于如下9个关键字
{Zhao,Qian,Sun,Li,Wu,Chen,Han,Ye,Dei}
13 8 9 6 11 1 4 12 2
设f(key)=向下取整[(Ord(第一个字母)-Ord(‘A’)+1)/2]
从这个例子可见,
1) 哈希函数是一个映象,即:将关键字的结婚映射到某个地址结合上,它的设置很灵活,只要这个地址集合的大小不超出允许范围即可。
2) 由于哈希函数是一个压缩映象,因此,在一般情况下,很容易产生“冲突“现象,即:key1<>key2,而f(key1)=f(key2)并且,改进哈希函数只能减少冲突,而不能避免冲突。
因此,在设计哈希函数时,一方面要考虑选择一个“好“的哈希函数;另一方面要选择一种处理冲突的方法。
二、哈希函数的构造方法
对数字的关键字可有下列哈希函数的构造方法,若非数字关键字,则需先对其进行数字化处理。
1. 直接定址法
2. 数字分析法
3. 平方取中法
4. 折叠法
5. 除留余数法
6. 随机数法
1. 直接定址法
哈希函数为关键字的线性函数H(key)=key 或者H(key)=a*key+b
仅限于:地址集合的大小=关键字集合的大小
2. 数字分析法
假设关键字集合中的每个关键字都是由s位数字组成(k1,k2,…,kn),分析关键字集中的全体,并从中提取分布均匀的若干位或它们的组合作为地址。
仅限于:能预先估计出全体关键字的每一位上各种数字出现的频度。
3. 平方取中
若关键字的每一位都有某些数字重复出现频度很高的现象,则先求关键字的平方值,以通过“平方“扩大差别,同时平方值的中间几位受到整个关键字中各位的影响。
4. 折叠法
若关键字的位数特别多,则可将其分割成几部分,憨厚取它们的叠加和为哈希地址。
可有:移位叠加和间界叠加两种处理方法。
5. 除留余数法
H(key)=key MOD p (p<=m表长)
关键问题是:如何选取p?
P应为不大于m的质数或是不含20以下的质因子
例如:当key=12,39,18,24,33,21时,若取p=9,则使所有含质因子3的关键字均映射到地址0,3,6上,从而增加了“冲突“的可能性。
6. 随机数法
H(key)=Random(key)
实际造表时,采用何种构造哈希数的方法取决于建表的关键字集合的情况(包括关键字的范围和形态),总的原则是使产生冲突的可能性降到尽可能地小。
三、处理冲突的方法
处理冲突的实际含义:为产生冲突的地址寻找下一个哈希地址。
1. 开放定址法
2. 链地址法
1.开放定址法
为长生冲突的地址H(key)求得一个地址序列:
H0,H1,H2,…,Hs 1<=s<=m-1
其中:H0=H(key) H1=(H(key)+Di) MOD m i=1,2,…s
增量di有三种取法
1) 线性探测在散列 di=c*I最简单的情况c=1
2) 平方探测散列 d1=12,-12,22,-22,….,
3) 随机探测在散列di是一组随机数列或者di=i*H(key)
注意:增量di应具有“完备性”
即:产生的Hi均不相同,且所产生的s(m-1)个Hi值能覆盖哈希表中所有的地址。则要求:
平方探测时的表长m必为4j+3的质数;
随机探测时的m和di没有公因子。
2.链地址法
将所有哈希地址相同的记录都链接在同一个链表中。
四、哈希表的查找
查找过程和造表的过程一致,假设采用开放定址处理冲突,则查找过程为:
对于给定值K,计算哈希地址i=H(K)
若r[i].key = NULLKEY则查找不成功
若r[i].key = KEY 则查找成功
否则求下一个地址Hi,直至
r[Hi].key = NULLKEY(查找不成功) 或r[Hi].key = K(查找成功)为止。
40_001 |
//开放定址哈希表的存储结构 int hashsize[] = {997,...}; typedef struct { ElemType *elem; int count; //当前数据元素个数 int sizeindex; //hashsize[sizeindex]当前容量 }HashTable; #define SUCCESS 1 #define UNSUCCESS 0 #define DUPLICATE -1
Status SearchHash(HashTable H, KeyType K, int &p, int &c) { //在开放定址哈希表H中查找关键字码为K的元素 p = Hash(K); //求得哈希地址 while(H.elem[p].key != NULLKEY && !EQ(K, elem[p].key)) collision(p, ++c); //求得下一个探查地址p if(EQ(K, H.elem[p].key)) return SUCCESS; //查找成功,p返回待查数据元素位置 else return UNSUCCESS; //查找不成功 }//SearchHash |
决定哈希表查找的ASL的因素:
1) 选用的哈希函数
2) 选用处理冲突的方法
3) 选用哈希表饱和程度,装载因子a=n/m值的大小
一般情况下,可以认为选用的哈希函数是“均匀”的,在讨论ASL时,可以不考虑它的因素。
哈希表的ASL是处理冲突方法和装载因子的函数。
可以证明:查找成功时有下列结果: