哈希(散列)

散列

  

导入

  之前,我们学过在顺序表中如何查找一个元素。

  如果这个顺序表里的元素无序,我们可以从表头开始依次进行比较,判断 a [ i ] a[i] a[i] k e y key key 是否相等。

  如果这个顺序表里的元素有序,我们可以利用其单调性进行二分查找从而提高平均查找效率。

  问题!!!

  无论是顺序查找还是二分查找,在查找过程中“比较”都不可避免,但是这是否必要?我们能不能直接通过关键字 k e y key key来确定它的存储位置呢?

  答: 散列表

在这里插入图片描述

定义

  散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数 ( 哈希函数 ) (哈希函数) (哈希函数),存放记录的数组叫做散列表 ( 哈希表 ) (哈希表) (哈希表)

优势与弊端

  优势:散列技术最适合的求解问题是查找与给定值相等的记录。对于查找来说,简化了比较过程,效率就会大大提高。

  弊端:对于相同关键码值对应很多记录的情况就不适合用散列。

PS:

  算法和数据结构往往是互不分开的。离开了算法,数据结构就显得毫无意义,而没有了数据结构算法就没有实现的条件。良好的数据结构思想就是一种高效的算法,但是数据结构不等于算法。只有当数据结构用于处理某个特定问题类型的时候,数据结构才会体现为算法。

  散列主要是面向查找的数据结构

在这里插入图片描述


  
  

散列函数的构造

构造准则

散列函数的构造准则:简单、均匀

(1)散列函数的计算简单,快速;

(2)使哈希地址均匀地分布在地址集 { 0 , 1 , … , m − 1 } \{0,1,…,m-1\} {0,1m1}上,并且冲突最小。

构造方法

(1)直接定址法

  取关键字或关键字的某个线性函数值为哈希地址: f ( k e y ) = a ∗ k e y + b f(key) = a*key + b f(key)=akey+b

  其中 a a a b b b为常数,这种哈希函数叫做自身函数。当 a = 1 , b = 0 a=1,b=0 a=1b=0时, f ( k e y ) = k e y f(key)=key f(key)=key

  注意:由于直接定址所得地址集合和关键字集合的大小相同,因此,对于不同的关键字不会发生冲突。但是,因为需要提前确定关键字的取值范围,且取值范围不能太大,所以,实际中能使用直接定址法的场景少之又少。

(2)相乘取整法

  首先用关键字 k e y key key乘上某个常数 A ( 0 < A < 1 ) A(0 < A < 1) A(0<A<1),并抽取出 k e y ∗ A key*A keyA的小数部分;然后用 m m m乘以该小数后取整。

  注意:该方法最大的优点是 m m m的选取比除留余数法要求更低。比如,完全可选择它是 2 2 2的整数次幂。虽然该方法对任何 A A A的值都适用,但对某些值效果会更好。 K n u t h Knuth Knuth建议选取 0.61803 0.61803 0.61803……。

(3)平方取中法

  当无法确定关键字中哪几位分布较均匀时,先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。

  通过平方扩大差别,另外,中间几位与关键字中的每一位都相关,故不同关键字会以较高的概率产生不同的、均匀的哈希地址。这是一种较常用的构造哈希函数的方法。

  举例:将一组关键字 ( 0100 , 0110 , 1010 , 1001 , 0111 ) (0100,0110,1010,1001,0111) (01000110101010010111)

  平方后得(0010000,0012100,1020100,1002001,0012321)

  若取表长为 1000 1000 1000,则可取中间的三位数作为散列地址集: ( 100 , 121 , 201 , 020 , 123 ) (100,121,201,020,123) (100121201020123)

(4)伪随机数法

  选择一个伪随机函数,取关键字的随机函数值为它的哈希地址,即 f ( k e y ) = r a n d o m ( k e y ) f(key) = random (key) f(key)=random(key),其中 r a n d o m random random为伪随机函数。通常,当关键字长度不等时采用此法构造哈希函数较恰当。

(5)数字分析法

  假设已经知道哈希表中所有的关键字值,而且关键字值都是数字,则可以取关键字值的若干位数字组成哈希地址,这种方法叫做数字分析法。

  举例:有 1000 1000 1000个记录,关键字为 10 10 10位十进制整数 x 1 , x 2 , x 3 … x 10 x1,x2,x3…x10 x1,x2,x3x10,如哈希表长度为2000。假设经过分析,各关键字中 x 3 、 x 5 x3、x5 x3x5 x 7 x7 x7的取值分布近似随机,则可使哈希函数为: f ( k e y ) = x 3 x 5 x 7 f(key)=x3x5x7 f(key)=x3x5x7。例如,f(3778597189)=757,f(9166372560)=632。

(6)除留余数法

  假设散列表长为 m m m,其散列函数公式定义为: f ( k e y ) = k e y   m o d   p      ( p ≤ m ) f(key) = key\:mod\: p \,\,\,\,(p ≤ m) f(key)=keymodp(pm)

   m o d mod mod表示求余数。这是一种最简单,也最常用的构造哈希函数的方法。它不仅可以对关键字直接取模,也可在对关键字进行折迭、平方取中等运算之后取模。注意:在使用除留余数法时,对p的选择很重要,通常p为小于或等于表长(最好接近m)的最大质数

多么 🐂× 的哈希函数,也只能说尽量减少哈希冲突, 绝对不可能避免哈希冲突

  
  

冲突解决方法

  哈希函数其实是一个压缩映像,由于通过哈希函数产生的哈希值是有限的,而数据较多时就会导致经过哈希函数处理后仍然会有不同的关键字对应相同的哈希值,因此哈希冲突不可避免。那么在建造哈希表时不仅要设定一个好的哈希函数,还要设定处理冲突的方法。

1.开放定址法

(1)线性探测

  按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上往后加一个单位,直至不发生哈希冲突。(易产生堆积现象)

   f ( k e y ) = ( f ( k e y ) + d i )     M O D     m    ( d i = 1 , 2 , 3 , … … , m − 1 ) f(key) = (f(key)+d_i) \,\,\,MOD\,\,\, m \,\,(d_i=1,2,3,……,m-1) f(key)=(f(key)+di)MODm(di=1,2,3,……,m1)

(2)二次探测

按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。(增加平方运算目的是为了不让关键字都聚集在某一块区域)

   f ( k e y ) = ( f ( k e y ) + d i )     M O D     m    ( d i = 1 2 , − 1 2 , 2 2 , − 2 2 , … … , q 2 , − q 2 ,   q ≤ m / 2 ) f(key) = (f(key)+d_i) \,\,\,MOD\,\,\, m \,\,(d_i=1^2,-1^2,2^2,-2^2,……,q^2,-q^2,\,q \leq m / 2 ) f(key)=(f(key)+di)MODm(di=12,12,22,22,……,q2,q2,qm/2)

(3)伪随机探测

  按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值的基础上加上随机数,直至不发生哈希冲突。

   f ( k e y ) = ( f ( k e y ) + d i )     M O D     m    ( d i 是一个伪随机序列 ) f(key) = (f(key)+d_i) \,\,\,MOD\,\,\, m \,\,(d_i是一个伪随机序列) f(key)=(f(key)+di)MODm(di是一个伪随机序列)

2.建立公共溢出区

  建立公共溢出区存储所有哈希冲突的数据。

3.再哈希法

  对于冲突的哈希值再次进行哈希处理,直至没有哈希冲突。

4.链式地址法

  对于相同的值,使用链表进行连接。使用数组存储每一个链表。

  优点:

  (1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;

  (2)由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;

  (3)开放定址法为减少冲突,要求装填因子 α α α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取 α ≥ 1 α≥1 α1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;

  (4)在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
  
  

查找性能

  散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。在介绍的四种处理冲突的方法中,产生冲突后的查找仍然是给定值与关键码进行比较的过程。所以,对散列表查找效率的量度,依然用平均查找长度来衡量。

  查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:

​  1、散列函数是否均匀;

​  2、处理冲突的方法;

  3、散列表的装填因子。

  散列表的装填因子定义为: α = 填入表中的元素个数 / 散列表的长度 α = 填入表中的元素个数 / 散列表的长度 α=填入表中的元素个数/散列表的长度

  α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。

  因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷。这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷。

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值