假如说我们要寻找一个人,我们如果把每一个地点都找遍,那样是没有效率的。我们可以想一想这个人通常会去什么地方,然后直接去那个地方找。这就是散列技术。
存储位置=f(关键字)
我们把以上对应关系f称为散列函数,又称为哈希(Hash)函数,采用散列技术将记录存储在一块连续的存储空间中,这块存储空间称为散列表(哈希表)。
散列冲突:对于两个关键字key1 != key2,却有f(key1)=f(key2),在单向函数上理解,就是不同的x,有同一个y值,这个时候就冲突了。
关于构造散列函数的方法和处理散列冲突的方法这里就不多说了,网上和书本能找到很多资料:
直接定址法、数字分析法(提取法)、平方取中法、折叠法、除留余数法(取余法)、随机数法、转换基数法等等。
处理散列函数冲突的方法:
开放定址法、链地址法、桶定址等等。
下面着重介绍散列表查找实现:
散列表查找代码实现
首先我们定义结构,HashTable就是散列表结构。结构当中的elem为一个动态数组。
#define SUCCESS 1
#define UNSSCCESS 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; //12
H->count = m;
H->elem=(int *)malloc(m*sizeof(int));
for(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; //说明关键字不存在
}
}
}
散列表的装填因子
装填因子 α = 填入表中的记录个数 / 散列表的长度
α标志着散列表的装满程度。当填入表中的记录越多,α就越大,产生冲突的可能性就越大。