哈希表
哈希表(Hash Table)也叫散列表,是根据关键码值(Key Value)而直接进行访问的数据结构。它通过把关键码值映射到哈希表中的一个位置来访问记录,以加快查找的速度。这个映射函数就做散列函数,存放记录的数组叫做散列表。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
例如:数据集合{180,750,600,430,541,900,460
用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快
哈希冲突
按照上述哈希方式,向集合中插入元素43,会出现什么问题?
&nbps; 对于两个数据元素的关键字 i 和 j (i != j),有 i!= j ,但有: HashFun(i) == HashFun(j)
即不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。
哈希函数
引起哈希冲突的一个原因可能是:哈希函数设计不够合理。
哈希函数设计原则:
- 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域
必须在0到m-1之间
- 哈希函数计算出来的地址能均匀分布在整个空间中
- 哈希函数应该比较简单
【常见哈希函数】
1)直接定址法
**取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B**
优点:简单、均匀
缺点:需要事先知道关键字的分布情况,适合查找比较小且连续的情况
面试题:找出一个字符串中第一个只出现一次的字符,要求:时间复杂度O(N),空间复杂度O(1)
2)除留余数法
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:
Hash(key) = key% p(p<=m),将关键码转换成哈希地址
3)平方取中法
假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;
再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
**平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况**
4)折叠法
折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加
求和,并按散列表表长,取后几位作为散列地址 折叠法适合事先不需要知道关键字的分布,适合关键
字位数比较多的情况
5)随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random
为随机数函数
通常应用于关键字长度不等时采用此法
处理哈希冲突
解决哈希冲突两种常见的方法是:闭散列和开散列
闭散列
闭散列:也叫开放地址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到表中“下一个” 空位中去 。
那如何寻找下一个空位?
1.线性探测
设关键码集合为{37, 25, 14, 36, 49, 68, 57, 11},散列表为HT[12],表的大小m = 12,假设哈希函数为:
Hash(x) = x %p(p = 11,是最接近m的质数),就有:
Hash(37) = 4
Hash(25) = 3
Hash(14) = 3
Hash(36) = 3
Hash(49) = 5
Hash(68) = 2
Hash(57) = 2
Hash(11) = 0
其中25,14,36以及68,57发生哈希冲突,一旦冲突必须要找出下一个空余位置
线性探测找的处理为:从发生冲突的位置开始,依次继续向后探测,直到找到空位置为止
【插入】
1. 使用哈希函数找到待插入元素在哈希表中的位置
2. 如果该位置中没有元素则直接插入新元素;如果该位置中有元素且和待插入元素相同,则不用插入;如果
该位置中有元素但不是待插入元素则发生哈希冲突,使用线性探测找到下一个空位置,插入新元素;
下面是闭散列哈希表的实现参考代码:
哈希表的数据结构:
typedef int KeyType;