1.概念
散列表(也叫哈希表)是一种查找算法,在查找时不需要进行一系列和关键字的比较操作。
散列表算法希望能尽量做到不经过任何比较,通过一次存取就能得到所查找的数据元素,因而必须要在数据元素的存储位置和它的关键字之间建立一个确定的对应关系,使每个关键字和散列表中一个唯一的存储位置相对应。因此在查找时,只要根据这个对应关系找到给定关键字在散列表中的位置即可,这种对应关系被称为散列函数。
2.散列函数
散列函数是从关键字到地址区间的映像。
好的散列函数能够使得关键字经过散列后得到一个随机的地址,以便使一组关键字的散列地址均匀地分布在整个地址区间中,从而减少冲突。
常用的构造散列函数方法:
1).直接定址法
取关键字或关键字的某个线性函数值为散列地址,即:
H(key) = key 或 H(key) = a * key + b
2).数字分析法
3).平方取值法
取关键字平方后的中间几位为散列地址。
4).折叠法
将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为散列地址。
5).除留余数法
取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址,即:
H(key) = key MOD p p ≤ m
6).随机数法
选择一个随机函数,取关键字的随机函数值为它的散列地址,即:
H(key) = random(key)
3.处理冲突
对不同的关键字可能得到同一散列地址,即key1 ≠ key2,而h(key1)= h(key2),这种现象称为冲突。具有相同函数值的关键字对该散列函数来说称作同义词。
在一般情况下冲突是不可避免的,因此,在创建散列表时不仅要设定一个好的散列函数,而且还要设定一种处理冲突的方法。
常用的处理冲突方法:
1.开放定址法
指可存放新表项的空闲地址既向它的同义词开放,也向它的非同一次开放。
Hi =(H(key) + di) MOD m i =0,1,2,…,k(k ≤ m-1);m表示表长;di为增量序列。
di取法:
1). 线性探测再散列:
di = 0,1,2,3,…,m-1
2). 平方探测法:
di = 02,12,-12,22,-22,…,k2,-k2(k≤m/2)
散列表长度必须是一个可以表示成4k+3的素数.
3). 随机探测再散列:
di = 伪随机数序列.
2.再散列法
Hi = rhi(key) i = 1,2,…,k
rhi均是不同的散列函数。
3.链地址法
将所有关键字为同义词的数据元素存储在同一线性链表中。
4.建立一个公共溢出区
3.性能分析
散列表查找效率取决于三个因素:散列函数,处理冲突方法,填装因子。
填装因子:α=表中记录数n/散列表长度m
散列表平均查找长度依赖于α而不依赖于n,m。直观地看,α越大,表越满,越容易冲突。
4.函数实现
//散列表
typedef struct{
int* elem;
int count;
}HashTable;
//初始化
Status InitHashTable(HashTable *H){
H->count = hash_size;
H->elem = (int *)malloc(hash_size*sizeof(int));
for(i = 0;i<hash_size;i++)
H->elem[i] = NO_KEY;
return true;
}
/散列函数
int Hash(int key){
return key % m;
}
//插入
void InsertHash(HashTable *H,int key){
int addr = Hash(Key);
while(H->elem[addr] != NO_KEY)
addr = (addr + 1) % m;
H->elem[addr] = key;
}
//查找
Status SearchHash(HashTable H,int key){
int addr = Hash(key);
while(H.elem[addr] != key){
addr = (addr + 1) % m;
//遇见空或者回到原点
if(H.elem[addr) == NO_KEY || addr == Hash(key))
return false;
}
return true;
}