初识hash

来源:互联网 发布:java实现支付功能 编辑:程序博客网 时间:2024/04/27 15:31

1.哈希表

    哈希表(Hash Table)是一种根据关键字直接访问内存存储位置的数据结构。通过哈希表,数据元素的存放位置和数据元素的关键字之间建立起某种对应关系,建立这种对应关系的函数称为哈希函数,也可以叫做散列函数,存放记录的数组叫做散列表,h(x)为哈希函数。


2.基本概念

  • 若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数(Hash function),按这个思想建立的表为散列表
  • 对不同的关键字可能得到同一散列地址,即k1不等于k2,而f(k1)=f(k2),这种现象称碰撞(Collision)。如下图。

     具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数Hash(k)和处理碰撞的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以  关键字在地址集中的“象”作为记录在表中的存储位置,这种表便称为散列表,这一映象过程称为散列造表散列,所得的存储位置称散列地址

  • 若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少碰撞。

3.构造散列函数

   散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快定位。哈希表的构造方法是:假设要存储的数据元素个数为n,设置一个长度为m(m≥n)的连续存储单元,分别以每个数据元素的关键字为自变量,通过哈希函数,把映射为内存单元的某个地址,并将该数据元素存储在该内存单元中。

从数学的角度来看,哈希函数实际上是关键字到内存单元的映射,因此我们希望通过哈希函数通过尽量简单的运算使得通过哈希函数计算出的哈希地址尽 量均匀地被映射到一系列的内存单元中。构造哈希函数有三个要点:第一,运算过程要尽量简单高效,以提高哈希表的插入和检索效率;第二,哈希函数应该具有较好的散列性,以降低哈希冲突的概率;第三,哈希函数应具有较大的压缩性,以节省内存。

  1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即hash(k)=k或hash(k)=a*k+b,其中a,b为常数(这种散列函数叫做自身函数)优点是不会产生冲突,但缺点空间复杂度可能会很高,适用于元素较少的情况下
  2. 数字分析法:假设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
  3. 平方取中法:取关键字平方后的中间几位为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,取其中的哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定。
  4. 折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
  5. 随机数法: H(key)=random(key) ,其中random为随机函数。通常用于关键字长度不等时采用此法
  6. 除留余数法:取关键字被某个不大于散列表表长C的数K除后所得的余数为散列地址。该方法计算简单,适用范围广,是最经常使用的一种哈希函数,可以表示为:,该方法的关键是常数的选取,一般取素数,若p选择不好,容易产生碰撞一般要求是接近或等于哈希表本身的长度,理论研究表明,该常数取素数时效果最好。

4.处理碰撞

 在构造哈希表时,存在这样的问题,对于两个不同的关键字,通过我们的哈希函数计算哈希地址时却得到了相同的哈希地址,我们将这种现象称为哈希冲突.

  哈希冲突主要与两个因素相关:第一,填装因子,所谓的填装因子是指哈希表中已存入的数据元素个数n与哈希地址空间大小的m比值,即α=n/m,α越 小,冲突的可能性就越小,相反则冲突可能性越大;但是α越小,哈希表的存储空间利用率也就很低,α越大,存储空间的利用率也就越高,为了兼顾哈希冲突和存 储空间利用率,通常将α控制在0.6-0.9之间,而.NET中的Hashtable则直接将α的最大值定义为0.72(注:虽然微软官方MSDN中声明 Hashtable默认填装因子为1.0,事实上所有的填装因子都为0.72的倍数);第二,与所用的哈希函数有关,如果哈希函数选择得当,就可以使哈希地址尽可能的均匀分布在哈希地址空间上,从而减少冲突的产生,但一个良好的哈希函数的得来很大程度上取决于大量的实践,不过幸好前人已经总结实践了很多高 效的哈希函数,可以参考园子里大牛Lucifer的文章:数据结构 : Hash Table [I]。

哈希冲突通常是很难避免的,解决哈希冲突有很多种方法,通常分为两大类:

4.1开放定址法

   它是一类以发生哈希冲突的哈希地址为自变量,通过某种哈希函数得到一个新的空闲内存单元地址的方法(如图),开放定址法的哈希冲突函数通常是一组;


4.1.1线性探查法(Linear Probing)

该方法的基本思想是:将散列表T[0..m-1]看成是一个循环向量,若初始探查的地址为d(即 h(key)=d),则最长的探查序列为:d,d+l,d+2,…,m-1,0,1,…,d-1 .即:探查时从地址d开始,首先探查T[d],然后依次探查T[d+1],…,直到T[m-1],此后又循环到T[0],T[1],…,直到探查到 T[d-1]为止。探查过程终止于三种情况:

(1)若当前探查的单元为空,则表示查找失败(若是插入则将key写入其中);

(2)若当前探查的单元中含有key,则查找成功,但对于插入意味着失败;

(3)若探查到T[d-1]时仍未发现空单元也未找到key,则无论是查找还是插入均意味着失败(此时表满)。

利用开放地址法的一般形式,线性探查法的探查序列为:

hi =(h(key)+i)%m0≤i≤m-1 //即di =i

hi=(h(key)+di) mod mi=1,2,...,k(k<=m-1)

其中m为表长,di为增量序列

如果di值可能为1,2,3,...m-1,称线性探测再散列。

如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,...k*k,-k*k(k<=m/2)

称二次探测再散列。

如果di取值可能为伪随机数列。称伪随机探测再散列。开放地址法堆装填因子的要求

开放定址法要求散列表的装填因子α≤l,实用中取α为0.5到0.9之间的某个值为宜。


4.1.2二次探查法(quadratic probing)

二次探查法的探查序列是:

hi=(h(key)+i*i)%m0≤i≤m-1 //即di=i^2

即探查序列为d=h(key),d+1^2,d+2^2,d+3^2…

该方法的缺陷是不易探查到整个散列空间。

4.1.3双重散列法(double hashing)

该方法是开放定址法中最好的方法之一,它的探查序列是:

hi=(h(key)+i*h1(key))%m0≤i≤m-1 //即di=i*h1(key)

即探查序列为:

d=h(key),(d+h1(key))%m,(d+2h1(key))%m,…,等。

该方法使用了两个散列函数h(key)和h1(key),故也称为双散列函数探查法。

 

4.2链表法

   当未发生冲突时,则直接存放该数据元素;当冲突产生时,把产生冲突的数据元素另外存放在单链表中。


链接法解决冲突的做法是:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组t[0..m-1]。凡是散列地址为i的结点,均插入到以t为头指针的单链表中。t中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。

 

链地址法处理冲突时的Hash表

4.3再哈希法

  均是不同的哈希函数,即在同一词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生。这种方法不易产生“聚集”,但是增加了计算的时间。

4.4.建立公共溢出区公共溢出区

   假设哈希函数的值域为[0,m-1],则设向量hashtable[0..m-1]为基本表,另外设立存储空间向量overtable[0..v]用以存储发生冲突的记录。

性能分析

插入和删除的时间均取决于查找,故下面只分析查找操作的时间性能。

虽然散列表在关键字和存储位置之间建立了对应关系,理想情况是无须关键字的比较就可找到待查关键字。但是由于冲突的存在,散列表的查找过程仍是一个和关键字比较的过程,不过散列表的平均查找长度比顺序查找、二分查找等完全依赖于关键字比较的查找要小得多。

(1)查找成功的asl

散列表上的查找优于顺序查找和二分查找。

(2) 查找不成功的asl

对于不成功的查找,顺序查找和二分查找所需进行的关键字比较次数仅取决于表长,而散列查找所需进行的关键字比较次数和待查结点有关。因此,在等概率情况下,也可将散列表在查找不成功时的平均查找长度,定义为查找不成功时对关键字需要执行的平均比较次数。

注意

①由同一个散列函数、不同的解决冲突方法构造的散列表,其平均查找长度是不相同的。

②散列表的平均查找长度不是结点个数n的函数,而是装填因子α的函数。因此在设计散列表时可选择α以控制散列表的平均查找长度。

③ α的取值

   α越小,产生冲突的机会就小,但α过小,空间的浪费就过多。只要α选择合适,散列表上的平均查找长度就是一个常数,即散列表上查找的平均时间为o(1)。

④ 散列法与其他查找方法的区别

除散列法外,其他查找方法有共同特征为:均是建立在比较关键字的基础上。其中顺序查找是对无序集合的查找,每次关键字的比较结果为"="或"!="两种可能,其平均时间为o(n);其余的查找均是对有序集合的查找,每次关键字的比较有"="、"<"和"& gt;"三种可能,且每次比较后均能缩小下次的查找范围,故查找速度更快,其平均时间为o(lgn)。而散列法是根据关键字直接求出地址的查找方法,其查 找的期望时间为o(1)。

例子:选取哈希函数h(k)=(3k)%11,用线性探测再散列法处理冲突。

试在0~10的散列地址空间中,对关键序列22,41,53,46,30,13,01,67构造哈希表,并求等概率情况下查找不成功的平均查找长度asl。







1 0
原创粉丝点击