1. 散列表查找定义:
存储位置 = f(关键字)
那样我们可以通过查找关键字不需要比较就可以获得需要记录的存储位置,称为散列技术。
散列技术: 是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key).
查找时:根据这个对应的确定关系找到给定值key的映射f(key).。映射关系:f称为散列函数,又称为哈希函数(Hash)。
采用散列技术奖记录存储在一块连续的存储空间中,这块连续的存储空间称为散列表或者哈希表。
2. 哈希函数构造法
2.1. 除留余数法
此方法为最常用的构造函数方法。对于哈希表长度为m的哈希函数公式: f(key) = key mod p (p $\leq$ m )
其中mod是指取模,求余数。
经验:若哈希表长度为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数。
2.2 随机数法
选择一个随机数,取关键字的随机函数值为它的散列地址,也就是 f(key) = random(key).
当关键字长度不等时,采用这个方法构造散列函数比较合适。
2.3 总结
现实中根据不同情况选择不同的散列函数。
(1).计算散列地址所需的时间
(2).关键字的长度
(3).散列表的大小
(4).关键字的分布情况
(5).记录查找的频率
3.处理散列冲突的方法
3.1 定义
两个关键字 key1 $\neq$ key2, 但是f(key1) = f(key2)。
接下来为解决冲突的方法。
3.2 开放寻址法
所谓的开放寻址法就是一旦发生了冲突,就去寻找下一个空闲的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
$f_i(key) = ( f (key) + d_i ) MOD m (d_i = 1,2,3,...,m-1)$
(1)散列表定义
#define SUCCESS 1 #define UNSUCCESS 0 #define HASHSIZE 12 /*哈希表长为数组的长度*/ #define NULLKEY -32768 typedef struct { int *elem; /*数据元素存储基础,动态分配数组*/ int count; /*当前数据元素个数*/ } HashTable; int m = 0; /*散列表表长,全局变量*/
(2) 哈希表初始化
/*初始化列表*/ 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 OK;
(3) 哈希函数
/*散列函数*/ int Hash(int key) { return key % m; /*除留余数法*/ }
(4) 将关键字插入散列表
/*插入关键子进散列表*/ void InsertHash(HashTable *H, int key) { int addr = Hash(key); while (H->elem[addr] != NULLKEY) /*如果不为,则冲突*/ addr = (addr+1) % m; /*开放定址法的线性探测*/ H->elem[addr] = key; }
(5)散列表查找关键字
Status SearchHash (HashTable H, int key, int *addr) { *addr = Hash(key); while(H.elem[*addr] != key) /*如果不为空,则冲突*/ { *addr = (*addr + 1) % m; /*开放定址的线性探测*/ if ( H.elem[*addr] == NULLPTR || *addr == Hash(key) ) { /*如果循环回到原点*/ return UNSUCCESS; } } return SUCCESS; }
3.3 链地址法
将所有关键字为同义词的记录存储在一个单链表中,而哈希表只存储所有同义词字表的头指针。
链地址法对于可能会造成很多冲突的散列函数来说,提供了绝对不会找不到地址的保障。
当然,也带来了查找时需要遍历单链表的性能损耗。