哈希函数的构造方法以及冲突解决办法

哈希函数的构造方法


常用的构造哈希函数的方法有:

    1.直接定址法

    取关键字或关键字的某个线性函数值为哈希地址。即:
H(key)=key 或 H(key)=a·key+b
    其中a和b为常数(这种哈希函数叫做自身函数)。

    [例如]:有一个从1岁到100岁的人口数字统计表,其中,年龄作为关键字,哈希函数取关键字自身。这样,若要询问25岁的人有多少,则只要查表的第25项即可。

    [又如]:有一个解放后出生的人口调查表,关键字是年份,哈希函数取关键字加一常数 H(key)=key+(-1948),若要查1970年出生的人数,则只要查第(1970-1948)=22项即可。

    由于直接定址所得地址集合和关键字集合的大小相同。因此,对于不同的关键字不会发生冲突。但实际中能使用这种哈希函数的情况很少。


   2.数字分析法 

    假设关键字是以r为基的数(如:以10为基的十进制数),若哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。

    [例如],有80个记录,其关键字为8位十进制数。假设哈希表的表长为10010,则可取两位十进制数组成哈希地址。取哪两位?原则是使得到的哈希地址尽量避免产生冲突,则需从分析这80个关键字着手。




对关键字全体的分析中我们发现:第①②位都是“8 1”,第③位只可能取1、2、3或4,第⑧位只可能取2、5或7,因此这四位都不可取。由

于中间的四位可看成是近乎随机的,因此可取其中任意两位,或取其中两位与另外两位的叠加求和后舍去进位作为哈希地址。

3.平方取中法

    取关键字平方后的中间几位为哈希地址。这是一种较常用的构造哈希函数的方法。

    通常在选定哈希函数时不一定能知道关键字的全部情况,取其中哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定。

    [例如]:为BASIC源程序中的标识符建立一个哈希表。假设BASIC语言中允许的标识符为一个字母,或一个字母和一个数字。在计算机内可用两位八进制数表示字母和数字,假设表长为512=29,则可取关键字平方后的中间9位二进制数为哈希地址。例如,图9.23(b)列出了一些标识符及它们的哈希地址。 



  4.折叠法

    将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这方法称为折叠法(folding)。关键字位数很多,而且关键字中每一位上数字分布大致均匀时,可以采用折叠法得到哈希地址。如国际标准图书编号0-442-20586-4的哈希地址分别如


5.除留余数法

    取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。即
H(key)=key MOD p p≤m
    这是一种最简单,也最常用的构造哈希函数的方法。它不仅可以对关键字直接取模(MOD),也可在折迭、平方取中等运算之后取模。值得注意的是,在使用除留余数法时,对p的选择很重要。若p选的不好,容易产生同义词。

    由经验得知:一般情况下可以选p为质数或不包含小于20的质因素的合数。

6.随机数法 

    选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key)=random (key),其中random为随机函数。通常,当关键字长度不等时采用此法构造哈希函数较恰当。

    实际工作中需视不同的情况采用不同的哈希函数。通常,考虑的因素有:
    (1)计算哈希函数所需时间(包括硬件指令的因素);
    (2)关键字的长度; 
    (3)哈希表的大小; 
    (4)关键字的分布情况; 
    (5)记录的查找频率。 

处理冲突的方法

哈希函数可以减少冲突,但不能避免,因此,如何处理冲突是哈希造表不可缺少的另一方面。
假设哈希表的地址集为0~(n-1),冲突是指由关键字得到的哈希地址为j(0≤j≤n-1)的位置上已存有记录,则“处理冲突”就是为该关键字的记录找到另一个“空”的哈希地址。在处理冲突的过程中可能得到一个地址序列Hi , i=1,2,…,k, ( Hi∈[0,n-1])。即在处理哈希地址的冲突时,若得到的另一个哈希地址H1仍然发生冲突,则再求下一个地址H2,若H2仍然冲突,再求得H3。依次类推,直至Hk不发生冲突为止,则Hk为记录在表中的地址。通常用的处理冲突的方法有下列几种:
   

 1.开放定址法

    Hi=(H(key)+di)MOD m         i=1,2,…,k (k≤m-1) (9-25)
    其中:H(key)为哈希函数;m为哈希表表长;di为增量序列,可有下列三种取法:
        (1)di=1,2,3,…,m-1,称线性探测再散列;
        (2)di=12,-12,22,-22,33,…,±k2,(k≤m/2)称二次探测再散列;
        (3)di=伪随机数序列,称伪随机探测再散列。 

[例如],在长度为11的哈希表中已填有关键字分别为17,60,29的记录(哈希函数 H(key)=key MOD11),现有第四个记录,其关键字为38


    从上述线性探测再散列的过程中可以看到一个现象:当表中i,i+1,i+2位置上已填有记录时,下一个哈希地址为i、i+1,i+2和i+3的记录都将填入j+3的位置,这种在处理冲突过程中发生的两个第一个哈希地址不同的记录争夺同一个后继哈希地址的现象称作“二次聚集”,即在处理同义词的冲突过程中又添加了非同义词的冲突,显然,这种现象对查找不利。



但另一方面,用线性探测再散列处理冲突可以保证做到:只要哈希表未填满,总能找到一个不发生冲突的地址Hk,而二次探测再散列只有在哈希表长m为形如 4j+3(j为整数)的素数时才可能,随机探测再散列,则取决于伪随机数列。

2.再哈希法

    Hi=RHi(key) 
    RHi均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生。这种方法不易产生“聚集”,但增加了计算的时间。 

3.链地址法

    将所有关键字为同义词的记录存储在同一线性链表中。假设某哈希函数产生的哈希地址在区间[0..m-1]上,则设立一个指针型向量Chain ChainHash[m];其每个分量的初始状态都是空指针。凡哈希地址为i的记录都插入到头指针为ChainHash[i]的链表中。在链表中的插入位置可以在表头或表尾;也可以在中间,以保持同义词在同一线性链表中按关键字有序。

    [例9-3] 已知一组关键字为(19,14,23,01,68,20,84,27,55,11,10,79)则按哈希函数H(key)=key MOD13和链地址法处理冲突构造所得的哈希表如图9.26所示。


4.建立一个公共溢出区

    这也是处理冲突的一种方法。假设哈希函数的值域为[0..m-1],则设向量 HashTable[0..m-1]为基本表,每个分量存放一个记录,另设立向量OverTable[0..v]为溢出表。所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。 


哈希表的查找及其分析

 在哈希表上进行查找的过程和哈希造表的过程基本一致。给定K值,根据造表时设定的哈希函数求得哈希地址,若表中此位置上没有记录,则查找不成功;否则比较关键字,若和给定值相等,则查找成功;否则根据造表时设定的处理冲突的方法找“下一地址”,直至哈希表中某个位置为“空”或者表中所填记录的关键字等于给定值时为止。

从哈希表的查找过程可见:
    1)虽然哈希表在关键字与记录的存储位置之间建立了直接映象,但由于“冲突”的产生,使得哈希表的查找过程仍然是一个给定值和关键字进行比较的过程。因此,仍需以平均查找长度作为衡量哈希表的查找效率的量度。
    2)查找过程中需和给定值进行比较的关键字的个数取决于下列三个因素:哈希函数,处理冲突的方法和哈希表的装填因子。

//开放定址哈希表的存储结构
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指示插入位置,并返回SUCCESS;否则,以p指示插入位置,并返回UNSUCCESS。
//c用以计冲突次数,其初值为零,供建表插入时参考
p=Hash(k);//求得哈希地址
    while(H.elem[p].key != NULLKEY && !EQ(K,H.elem[p].key)) //该位置中填有记录并且关键字不相等
     collision(p, ++c);  //求得下一探测地址p
    if EQ(K, H.elem[p].key)
       return SUCCESS; //查找成功,p返回待查数据元素的位置
    else
        return UNSUCCESS; //查找不成功(H.elem[p].key==NULLKEY)
//p返回的是插入位置
}


通过调用查找算法,实现开放定址哈希表的插入操作

Status InsertHash(HashTable &H, ElemType e){
//查找不成功时插入数据元素e到开放定址哈希表H中,并返回OK;若冲突次数
//过大,则重建哈希表

   c=0;
   if(SearchHash(H,e.key,p,c))
      return DUPLICATE;//表中已有与e有相同关键字的元素
    else if(c<hashsize[H.sizeindex] /2){//冲突次数c未达到上限,c的阀值可调
     H.elem[p] = e; ++H.count; return OK;//插入e
}

   else{
     RecreateHashTable(H); return UNSUCCESS;
   }  //重建哈希表
}

本文摘自  清华大学出版社 数据结构(c语言版) 严蔚敏 吴伟民 编著
对原作者深表谢意
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值