初探神奇的哈希表

哈希表

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

顺序表其实是一种特殊的哈希表,
其中通过数组下表(key) 可以随机访问 数组中下标对应的元素值 value
其hash函数是 f(key) = key

哈希表是一种以(key-value)为存储对象的数据结构,可以做到通过哈希函数在 O ( 1 ) 情况下用key找到value;

我们知道顺序表也具有随机访问的能力,为什么不用顺序表代替哈希表能?
原因有两点
1. 顺序表的key只能限定为数组下标的形式,而哈希表没有限制,只要有对应的哈希函数即可. (通过哈希函数f(key) 可以找到对应的value) key可以是英文单词,汉字,字符串等等
2. 顺序表空间开销大,例如 我们有两个键值对 (1-“value1”),(1000-“value2”), 那么我们要保存这两个键值对,就要开辟从0 到10001个空间,
然而有效元素只有下标为 1和1000的两个位置,而通过哈希表的哈希函数则可以有效的利用有限的空间.例如一个哈希函数为 key%100 ,那么不管key多大,我们就可以将其插入到0到99的范围内

哈希函数的选取

[常见的哈希算法]

  • 直接定制法
    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
    优点:简单、均匀
    缺点:需要事先知道关键字的分布情况
    适合查找比较小且连续的情况
  • 除留余数法
    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key
    % p(p<=m),将关键码转换成哈希地址
  • 平方取中法
    假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;
    再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
    平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况
  • 折叠法
    折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表
    长,取后几位作为散列地址
    折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
  • 随机数法
    选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数
    通常应用于关键字长度不等时采用此法
  • 数学分析法
    设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均
    匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分
    布均匀的若干位作为散列地址。

解决哈希冲突的办法

我们可以看出,一个哈希函数的选取是至关重要的,一个好的哈希函数可以大大避免哈希冲突的产生,
我们注意到,刚才我们选取的函数是key % m , 假设有两个key,6 和 8,m是2, 他们的哈希函数计算结果是一样的,那么就会导致不同的key,返回的是一份value,显然不对.
我们可以看出,一个哈希函数的选取是至关重要的,一个好的哈希函数可以大大避免哈希冲突的产生,
如果发生的冲突,我们就要解决冲突,通常解决哈希冲突的方法有两个

闭散列

闭散列:也叫开放地址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到表中“下一个” 空位中去
寻找空位也有一下几种方法
- 线性探测
线性探测找的处理为:从发生冲突的位置开始,依次继续向后探测,直到找到空位置为止

我们可以看到线性探测实现起来非常简单,但是如果经过多次线性探测,
- 内存空间的有序性会被打乱, 例如上面的例子,key为70 会把原本是71的空间占掉,那么71 在插入时也必须进行线性探测.
- 过多的线性探测会使哈希表的查找性能急剧下降,每次在通过哈希函数算出对应空间地址后,还要比较此处key是否和查找的key相同
这时候我们必须引入一个装填因子来避免这种情况的发生,
填装因子定义如下

a = x / y

不难理解,装填因子反应了哈希表的拥挤程度, 通常情况下取 a = 0.7

  • 二次探测
    若当前key与原来key产生相同的哈希地址,则当前key存在该地址后偏移量为(1,2,3…)的二次方地址处
    key1:hash(key)+0
    key2:hash(key)+1^2
    key3:hash(key)+2^2

开散列 (哈希桶)

哈希桶处理冲突的方法就很直接了

如果发生哈希冲突,就在其位置用链表将下一个元素保存

猜你喜欢

转载自blog.csdn.net/kwinway/article/details/80419557
今日推荐