一、引言
- 哈希表查找也是一种查找方式,在此之前,如果了解其它查找方法,然后对比该方法最好
- 本篇文章主要从两个方面介绍哈希表,一是哈希表的构造方法,另一个是哈希表的处理冲突方法。
- 同时,本篇文章介绍元素类型为数字整型。
- 红色尾非常主要内容,蓝色为细节内容。
二、哈希表概念及原理
1. 哈希表定义:
哈希表(散列表), 是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列
2. 哈希表基本思想
- 可能不同的关键字会映射到同一个散列地址上,可能不同的关键字会映射到同一个散列地址上,即h(key i) = h(key j)(当key i ≠key j),称为“冲突(Collision)”。----需要某种冲突解决策略
-
甚至可以理解为:直接按照某个公式查找元素地址位置
-
哈希表的时间复杂度几乎为常量O(1),即与问题规模无关。
三、哈希表实现
PS:哈希表部分有两个十分重要的内容,哈希表的构造方法,哈希表的处理冲突方法
1. 简介部分
- 哈希表的构造方法
考虑因素:计算哈希函数的时间,关键字长度,哈希表大小,关键字分布情况,记录查找的频率(可从这几个方面来理解)
直接地址法,数字分析法,除留余数法,折叠法,平方取中法等...
- 哈希表处理冲突方法
考虑因素:同一地址存在多个元素 ,产生冲突。
开放定址法,链地址法,再哈希法,公共溢出法
2. 哈希表构造方法及其原理
-
直接地址法:取关键词的某个线性函数值为散列地址
H(key) = a x key +b ,相当于一次函数。例如:h(key)=key-1990
备注:由该地址所得地址集合与关键字集合大小相同,故不会产生冲突。但是实际极少使用。
-
除留余数法: h(key) = key mod p 例如:h(key) = key % 17通常来说,p取值为素数
备注:会产生冲突。很常用,但是p的取值要十分注意。
-
数字分析法:分析数字关键字在各位上的变化情况,取比较随机的位作为散列地址
例如上图的身份证号码:选择标红的6位,即变化可能性最大的6位作为散列地址
备注:适合处理关键字位数多的情况,事先知道关键字分布最好。
-
折叠法:把关键词分割成位数相同的几个部分,然后叠加
例如:56793542. 542+793+056=1391,哈希地址为391
备注:关键字位数很多,并且关键字每一位上的数字分布大致均匀可用
-
平方取中法:取关键字平方后的中间几位为散列地址
例如:56793542 56793542 x 56793542=322550641290564; 则取641为散列地址
备注:较为常用的方法。不需知道关键字的全部情况,平方后的中间几位与数每一位都相关,以此建立连接。
附加字符关键词散列函数构造:ASCII码加和法,前三个字符移位法,移位法。
3. 哈希表的处理冲突方法及其原理
PS: 大致分为两种:换个位置存储冲突元素(开放地址法),同一位置的冲突元素组织在一起(链地址法)
-
·开放地址法:一旦产生冲突,就按照某种规则去寻找另一个空地址,增量序列不同
分为: Ⅰ 线性探测法:以增量序列 1,2,……(TableSize -1)循环试探下一个存储地址
Ⅱ 二次探测:以增量序列 1^2, -1^2, 2^2, -2^2, … (TableSize/2)平方,- (TableSize/2)平方
Ⅲ 随机探测法:增量序列由随机函数产生,随机增量。
Ⅳ 再散列函数法:当散列表元素太大时,(通常为50%-85%最合适),加倍扩大散列表即可。
PS:TableSize表示哈希表的大小
- 再哈希法:Hi=R·Hi(key);即产生冲突时,计算另外一个哈希函数地址,直到不产生冲突。
-
链地址法:将相应位置冲突的suoy2关键词存储在同一个单链表中。(Redis数据库,STL模板库常用)
例如:设关键字序列为 { 47, 7, 29, 11, 16, 92, 22, 8, 3, 50, 37, 89, 94, 21 };
散列函数取为:h(key) = key mod 11;
查找成功的平均查找次数: ASLs =(9+5*2)/ 14 ≈ 1.36
- 公共溢出法:与再散列方法相似,为所有冲突的关键字建立一个公共溢出区存取地址。
4. 利用除留余数法构造,开发地址发解决冲突的算法实现。
#define SUCCESS 1
#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*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; //关键字不存在
}
return SUCCESS; //查找成功返回
}
四、哈希表运用场景
见下面链接:Redis中的Hash数据类型及其使用
http://www.cleey.com/blog/single/id/808.html