哈希表
通过查找关键字不需要比较就可获得需要的记录的存储位置。即存储位置=f(关键字)
这就是一种新的存储技术---散列技术(哈希技术)
散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在 f(key)的位置上。
这里我们把这种对应关系f成为散列函数,又称为哈希函数(Hash)。按这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续的存储空间即为散列表或哈希表(Hash table)。那么关键字对应的记录存储位置我们称为散列地址。散列技术既是一种存储方法,也是一种查找方法。
冲突
我们时常会碰到两个关键字key1!=key2,但是却有f(key1)=f(key2),这种现象称为冲突(collision),并把key1和key2称为这个哈希函数的同义词(synonym)。
哈希函数的构造方法
好的哈希函数有两个原则:
1.计算简单
如果哈希函数需要很复杂的计算,这对于频繁的查找来说,就会大大降低查找的效率。因此散列函数的计算时间不应该超过其他查找技术与关键字比较的时间。
2.散列地址分布均匀
这样可以保证存储空间的有效利用,并减少为处理冲突而耗费的时间。
一、直接定址法
f(key)=a*key+b(a,b为常数)
优点:简单均匀,不会产生冲突。
缺点:需事先知道关键字的分布情况,适合查找表较小且连续的情况。
二、数字分析法
这里提到一个关键字---抽取。抽取方法是使用关键字的一部分来计算散列存储位置的方法,这在散列函数中是常常用到的手段。
数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。
三、平方取中法
比如关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227,用作散列地址,再比如关键字是4321,那么它的平方就是18671041,抽取中间的3位就是671,也可以是710,用做散列地址。
平方取中法比较适合于不知道关键字的分布,而位数又不是很大的情况。
四、折叠法
折叠法是将关键字从左到右割成位数相等的几部分(最后一部分位数不够可以短些),然后将这几部分叠加求和,并按散列表表长 ,取最后几位作为散列地址。
折叠法事先不需要知道关键字的分布,适合关键字位数较多的情况。
五、除留取余法(较常用)
f(key)=key mod p(p<=m) 此方法不仅可以对关键字直接取模,也可以在折叠、平方取中后再取模。
本方法的关键在于选择合适的p,p如果选得不好,发生冲突的频率会大大增加。
根据前辈们的经验,若散列表的长度为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因数的合数
六、随机数法
取关键字的随机函数值为它的散列地址。也就是f(key)=random(key)。这里random是随机函数。
当关键字的长度不等时,采用这个方法构造散列函数是比较合适的。
处理散列冲突的方法
一、开放定址法
开放定址法指的是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
1.线性探测法
2.增加平方运算的目的是为了不让关键字都聚集在某一块区域。这种方法为二次探测法
3.在冲突时,对于位移量di采用随机函数得到,称之为随机探测法
二、再散列函数法
这里RHi就是不同的散列函数,可以把余数、折叠、平方取中全都用上。这种方法能够使得关键字不产生聚集。
三、链地址法
无论有多少冲突,都只是在当前位置给单链表增加结点
链地址法对于可能造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保障。
但同时也带来了查找时需要遍历单链表的性能损耗。
四、公共溢出区法
如果关键位置有冲突,那么就将它们存储到溢出表中:
在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行对比,如果相等,则查找成功;如果不相等,则到溢出表去进行顺序查找。如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是较高的。
哈希表查找的实现
#define UNSUCCESS 0
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct{
int *elem;
int count;
}HashTable;
int m=0;
Status InitHashTable(HashTable *H)
{
int i;
m=HASHSIZE;
H->count=m;
H->elem=(int *)malloc(m*siezof(int));
for(int i=0;i<m;++i) H->elem[i]=NULLKEY;
return OK;
}
int HASH (int key)
{
return key%m;
}
void InsertHash(HashTable *H,int key)
{
int addr=Hash(key);
while(H->elem[addr]!=NULLKEY)
addr=(addr+1)%m;
H->elem[addr]=key;
}
Status SearchHash(HashTable H,int key,int *addr)
{
*addr=Hash(key);
while(H.elem[*addr]!=key)
{
*addr=(*addr+1)%m;
if(H.elem[*addr]==NULLKEY||*addr==Hash(key))
return UNSUCCESS;
}
return SUCCESS;
}