1 常用的构造hash函数的方法
1.1 直接定址法
1、原理:取关键字或关键字的某个线性函数值为哈希值。、
2、公式:H(key)=key或H(key)=a*key+b
3、适合查找表较小且连续的情况
4、优点:简单、均匀,不会产生冲突
5、缺点:需要知道关键字的分布,现实中不常用
1.2 数字分析法
1、原理:抽取关键字中的一部分来计算存储位置。假设关键字是以r为基(如:以10为基的十进制),并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
2、适用于关键字较长的情况。
1.3 平方取中法
1、原理:取关键字平方后的中间几位作为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,取其中哪几位也不一定合适,而一个数平方后的中间几位和数的每一位都相关,由此使得随机分布的关键字得到的哈希地址也是随机的。
2、适用于不知道关键词分布,且位数不长的情况。比较常用。
1.4 折叠法
1、原理:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
2、适用于不知道关键字分布情况,且位数很多,关键字的每一位上数字分布大致均匀时。
3、在折叠法中数位叠加可以有移位叠加和间界叠加两种方法。
1)移位叠加:将分割后的每一部分的最低位对齐,然后相加;
2)间界叠加:从一端向另一端沿分割界来回折叠,然后对齐相加。
例如:每一种西文图书都有一个国际标准图书编号,它是一个10位的十进制数字,若要以它作为关键字建立一个哈希表,在馆藏图书不到10000时,可以采用折叠法构造一个四位的哈希函数。如国际标准图书编号0-442-20586-4的哈希地址分别如下图(a)(b).
1.5除留余数法
1、原理:取关键字被某个不大于哈希表长m的数p除后所得的余数为哈希地址。即H(key)=key MOD p,p<=m
2、这是最简单、最常用的一种方法。不仅可以对关键字直接取模,还可以在折叠、平方取中后取模。
3、p取小于等于m的最小质数或者不包含小于20的质因数的合数,以减少冲突的情况。
1.6 随机数法
1、原理:选择一个随机函数,取关键字的随机函数值作为它的哈希地址。即H(key)=random(key),其中random为随机函数。
2、适用于关键字长度不等的情况。
综上,实际工作中选择哈希函数的时候,要考虑的因素有:
1)计算哈希函数所需时间;
2)关键字的长度;
3)哈希表的大小;
4)关键字的分布情况;
5)记录的查找频率。
2 常用的冲突处理方法
2.1 开放定址法
1、原理:
其中:f(key)是哈希函数,m为哈希表表长,di为增量序列,可有以下三种取法:
1)di = 1,2,3,…,m-1,称线性探测再序列;
一旦发生冲突,就寻找下一个空的散列地址。只要哈希表未填满,总能找到一个不发生冲突的地址。
2)(k<=m/2),称二次探测再序列;
目的是不让关键字集中在某块区域,产生堆积。只有在哈希表长为形如4j+3(j为整数)的素数时,才有可能一定能找到一个不冲突的地址。
3)di=伪随机数序列,称伪随机探测再散列。
但查询时需要设置和插入时相同的随机种子。
2.2 再哈希法
1、原理: RHi均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数的地址,知道冲突不再发生。
2、这种方法不易产生“聚集”,但是增加了计算的时间。
2.3 链地址法
1、原理:将所有关键字为同义词的记录存储在同一线性链表中,在散列表中只存储所有同义词表的头指针。
2、假设某哈希函数产生的哈希值在区间[0,m-1]上,则设立一个指针型向量Chain ChainHash[m]。其每个分享的初始状态都是空指针。凡哈希地址为i的记录都插入到头指针为ChainHash[i]的链表中。在链表中的插入位置可以在表头或表尾,也可以在中间。
例如:已知一组关键字为(19,14,23,01,68,20,84,27,55,11,10,79),则按H(key)=key MOD 13和链地址法处理冲突构造所得的哈希表如下:
2.4 建立一个公共溢出区
1、原理:为所有冲突的关键字开辟一个公共的溢出区来存放。
假设哈希函数的值域为[0,m-1],则设向量HashTable[0..m-1]为基本表,每个分量存放一个记录,另设向量OverTable[0..v]为溢出表。所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。
2、适用于相对于基本表来说冲突数据较少的情况。
3 哈希表的查找
//--------开放定址哈希表的存储结构-------
int hashsize[] = {997,...}; //哈希表容量递增表,一个合适的素数序列
typdef 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 &c){
//在开放定址哈希表H中,查找关键码为K的元素,若查找成功,以p指示待查数据元素
//在表中的位置,并返回SUCCESS;否则,以p指示插入位置,并返回c用以计冲突次数
//其初值为零,供建表插入时参考
p = Hash(K) //求哈希地址
while(H.elem[p].key != NULLKEY
&& !EQ(K,H.elem[p].key)) //该位置中填有记录并且关键字不相等
collision(p,++c); //求得下一个探测地址
if EQ(K,H.elem[p].key)
return SUCCESS; //查找成功,p返回待查数据元素地址
else
return UNSUCCESS; //查找不成功,H.elem[p].key == NULLKEY;
}//SearchHash
Status InsertHash(HashTable &H,Elemtype e){
//查找不成功时,插入数据元素e到开放定制哈希表中,并返回OK,若冲突次数过大,
//则重建哈希表
c = 0;
if(SearchHash(H,e.key,p,c))
return DUPLICATE; //表中已有与e有相同关键字的元素
else if(c < hashsize[H.sizeinde] / 2){ //冲突次数c未达到上限,(c的阈值可调)
H.elem[p] = e; ++H.count; return OK;
}
else{
RecreateHashTable(H); return UNSUCCESS; //重建哈希表
}
}
在一般情况下,处理冲突方法相同的哈希表,其平均查找长度依赖于哈希表的装填因子。
哈希表的装填因子定义为:
α = 表中填入的记录数/哈希表的长度
阿尔法标志着哈希表的装满程度。α越小,发生冲突的的可能性就越小。
线性探测再散列的哈希表查找成功时的平均查找长度为:Snl ≈ 1/2( 1 + 1/(1 - α))
随机探测再散列、二次探测再散列和再哈希的平均查找长度为:Snr ≈ -1/a ln(1 - a)
链地址法处理冲突的平均查找长度为:Snc ≈ 1 + a/2