1、散列表查找定义
散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。采用散列技术将记录存储在一块连续的存储空间中,这块连续的存储空间称为散列表或哈希表(Hash Table)。散列技术既是一种存储方法,也是一种查找方法。
2、散列函数的构造方法
好的散列函数满足两个原则:一是计算简单,二是散列地址分布均匀。
1)直接定址法
取关键字的某个线性函数值为散列地址:f(key)=a*key+b,这种散列函数的优点就是简单、均匀,也不会产生冲突,但问题是需要事先知道关键字的分布情况,适合查找表较小且连续的情况。
2)数字分析法
数字分析法就是抽取关键字中的一部分来计算散列存储位置。数字分析法通常适合处理关键字位数较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。
3)平方取中法
取关键字的平方,抽取中间的3位用作散列地址。平方取中法比较适合于不知道关键字的分布,而位数又不是很大的情况。
4)折叠法
折叠法是将关键字从左到右分割成位数相等的几部分,然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。折叠法实现不需要知道关键字的分布,适合关键字位数较多的情况。
5)除留余数法
次方法为最常用的构造散列函数方法。对于散列表长为m的散列函数公式为:f(key)=key mod p(p<=m)。这方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。若散列表长为m,通常p为小于等于表长的最小质数或不包含小于20质因子的合数。
6)随机数法
选择一个随机数,取关键字的随机函数值为它的散列地址。也就是f(key)=random(key)。当关键字的长度不等时,采用这个方法构造散列函数比较合适的。
总结:在现实中,应该视不同的情况采用不同的散列函数。以下一些考虑的因素来提供参考:一是计算散列地址所需的时间,二是关键字的长度,三是散列表的大小,四是关键字的分布情况,五是记录查找的频率。综合这些因素,才能决策选择哪种散列函数更合适。
3、处理散列冲突的方法
1)开放定址法
开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
fi(key)=(f(key)+di) mod m(di=1,2,3,...,m-1),这种解决冲突的开放定址法称为线性探测法。
fi(key)=(f(key)+di) mod m(),这种方法对于位移量di采用随机函数计算得到称为随机探测法。
2)再散列函数法
对于再散列函数法,事先准备多个散列函数,fi(key)=RHi(key)(i=1,2,...,k),这里RHi就是不同的散列函数。每当发生散列地址冲突时,就换一个散列函数计算,相信总会有一个可以把冲突解决掉。这种方法能够使得关键字不产生聚集,相应地也增加了计算时间。
3)链地址法
将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。无论有多少个冲突,都只是在当前位置给单链表增加结点的问题。链地址法对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保障。这也就带来了查找时需要遍历单链表的性能损耗。
4)公共溢出区法
这种方法,对于冲突的关键字,为所有冲突的关键字建立了一个公共的溢出区来存放。在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行对比,如果相等,则查找成功;如果不相等,则到溢出表去进行顺序查找。如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是非常高的。
4、散列表查找实现
#include<iostream>
using namespace std;
typedef int Status;
#define TRUE 1
#define FALSE 0
#define NULLKEY -32768
#define HASHSIZE 12
typedef struct {
int* elem; //数据元素存储基址,动态分配数组
int count; //当前数据元素个数
}HashTable;
int m = 0; //散列表表长,全局变量
//初始化Hash表
Status InitHashTable(HashTable* H)
{
int i;
m = HASHSIZE;
H->count = m;
H->elem = (int*)malloc(m * sizeof(int));
for (i = 0; i < m; i++)
H->elem[i] = NULLKEY;
return TRUE;
}
//除留余数法计算Hash值,通过关键字计算存储地址
int Hash(int key)
{
return key % m;
}
//向Hash表插入关键字操作
void InsertHash(HashTable* H, int key)
{
int addr = Hash(key);
while (H->elem[addr] != NULLKEY)
addr = (addr + 1) % m;
H->elem[addr] = key;
}
//在Hash表中搜索关键字,返回关键字存储地址
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 FALSE;
}
return TRUE;
}
int main()
{
HashTable H;
InitHashTable(&H);
int elem[HASHSIZE] = { 12,67,56,16,25,37,22,29,15,47,48,34 };
for (int i = 0; i < HASHSIZE; i++)
InsertHash(&H, elem[i]);
int addr;
int key = 67;
if (SearchHash(H, key, &addr) == TRUE)
cout << addr << endl;
else
cout << "Search Nothing." << endl;
return 0;
}