Hash表、Hash冲突

     负载系数:元素个数除以表格大小。

     表格解释:hash底层是用数组实现的,表格大小也就是指数组大小。

     开方定址法:负载系数在0~1之间。

     链地址法:负载系数,将大于1。数组存储的是一个链表的头指针。

    STL采用的就是链地址法,数组用vector实现,链表用list实现,虽然针对链表的搜寻操作是线性的,但只要list够短,速度就够快。

      Hash表通过Hash函数将关键字映射到该记录在表中的存储位置(注意这里的存储位置只是表中的存储位置,并不是实际的物理地址,称作为Hash地址)。

     由于很容易产生冲突(不同的键值,经过Hash函数计算后Hash值是一样的,无论怎么处理,在访问时,针对同一Hash值的不同关键字都会产生比较操作,所以不仅要存储Value值,还要存储key,除非有标志位记录任一哈希是否冲突,否则所有都要存储key和value

     在哈希表上进行查找的过程和哈希造表的过程基本一致。对于给定的key值,根据造表时设计的哈希函数求得哈希地址,若表中此位置上没有记录,则查找不成功;如果有记录,比较key值,如果相等,返回对应value;否则根据造表时设定的处理冲突的方法找“下一地址”,直到哈希表中某个位置为空(不成功)或者找到了对应的记录

     Hash表也称散列表,也有直接译作哈希表,Hash表是一种特殊的数据结构,它同数组、链表以及二叉排序树等相比较有很明显的区别,它能够快速定位到想要查找的记录,而不是与表中存在的记录的关键字进行比较来进行查找。这个源于Hash表设计的特殊性,它采用了函数映射的思想将记录的存储位置与记录的关键字关联起来,从而能够很快速地进行查找。

     1. Hash函数的设计

     Hash函数设计的好坏直接影响到对Hash表的操作效率。下面举例说明:

     假如对上述的联系人信息进行存储时,采用的Hash函数为:姓名的每个字的拼音开头大写字母的ASCII码之和。

     因此address(张三)=ASCII(Z)+ASCII(S)=90+83=173;

     address(李四)=ASCII(L)+ASCII(S)=76+83=159;

   address(王五)=ASCII(W)+ASCII(W)=87+87=174;

   address(张帅)=ASCII(Z)+ASCII(S)=90+83=173;

      假如只有这4个联系人信息需要进行存储,这个Hash函数设计的很糟糕。首先,它浪费了大量的存储空间,空间利用率只有4/174,不到5%;另外,根据Hash函数计算结果之后,address(张三)和address(李四)具有相同的地址,这种现象称作冲突,对于174个存储空间中只需要存储4条记录就发生了冲突,这样的Hash函数设计是很不合理的。

所以在构造Hash函数时应尽量考虑关键字的分布特点来设计函数使得Hash地址随机均匀地分布在整个地址空间当中。通常有以下几种构造Hash函数的方法:

  1)直接定址法

  取关键字或者关键字的某个线性函数为Hash地址,即address(key)=a*key+b;如知道学生的学号从2000开始,最大为4000,则可以将address(key)=key-2000作为Hash地址。

  2)平方取中法

  对关键字进行平方运算,然后取结果的中间几位作为Hash地址。假如有以下关键字序列{421,423,436},平方之后的结果为{177241,178929,190096},那么可以取{72,89,00}作为Hash地址。

  3)折叠法

  将关键字拆分成几部分,然后将这几部分组合在一起,以特定的方式进行转化形成Hash地址。假如知道图书的ISBN号为8903-241-23,可以将address(key)=89+03+24+12+3作为Hash地址。

    4)除留取余法

    如果知道Hash表的最大长度为m,可以取不大于m的最大质数p,然后对关键字进行取余运算,address(key)=key%p。

在这里p的选取非常关键,p选择的好的话,能够最大程度地减少冲突,p一般取不大于m的最大质数。

   2.Hash表大小的确定

   Hash表大小的确定也非常关键,如果Hash表的空间远远大于最后实际存储的记录个数,则造成了很大的空间浪费,如果选取小了的话,则容易造成冲突。在实际情况中,一般需要根据最终记录存储个数和关键字的分布特点来确定Hash表的大小。还有一种情况时可能事先不知道最终需要存储的记录个数,则需要动态维护Hash表的容量,此时可能需要重新计算Hash地址。

    3.冲突的解决

    在上述例子中,发生了冲突现象,因此需要办法来解决,否则记录无法进行正确的存储。通常情况下有2种解决办法:

   1)开放定址法

   即当一个关键字和另一个关键字发生冲突时,使用某种探测技术在Hash表中形成一个探测序列,然后沿着这个探测序列依次查找下去,当碰到一个空的单元时,则插入其中。

   探测序列产生的常见策略(在Hash函数计算的Hash地址基础上,按照一定策略,寻找新的地址):

   Hi=(H(key)+di)%m (m为Hash表长)

    a) 线性探测再散列 di=1,2,3,4,……,m-1

    b) 二次探测再散列 di=12,-12,22,-22,…….,+-(m-1)2

    c) 伪随机探测再散列 di为伪随机数(所谓伪随机,就是一个固定的无规律序列,因为访问时还需要它,所以不能是真正随机的)

     2)再哈希法

    哈希表的开放定址法中的二次探测和伪随机探测虽然消除了线性探测的首次聚集问题,但是又产生了新的问题:二次聚集。二次聚集产生的原因是:所有映射到同一个单元的关键字,在探测过程中执行了相同的序列。即二次聚集会发生是因为探测序列总是相同的。即步长只依赖于哈希函数,与关键字无关。

     再哈希法是同时构造多个不同的哈希函数,当冲突时,利用下一个哈希函数进行计算。这种方法不易产生聚集,但是会增加计算时间。

     Hi=RHi(key)  RHi是不同的哈希函数。

     3 ) 链地址法

     采用数组和链表相结合的办法,将Hash地址相同的记录存储在一张线性表中,而每张表的表头的序号即为计算得到的Hash地址。如上述例子中,采用链地址法形成的Hash表存储表示为:


     4.Hash表的优缺点

    优点:Hash表存在的优点显而易见,能够在常数级的时间复杂度上进行查找,并且插入数据和删除数据比较容易。

    缺点:

1)它是基于数组的,数组创建后难于扩展,某些哈希表被基本填满时,性能下降得非常严重,所以程序员必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中,这是个费时的过程)。

2)哈希表按照 key 对 value 有序枚举(ordered enumeration, 或者称有序遍历)是比较麻烦的(比如:相比于有序搜索树),需要先取出所有记录再进行额外的排序。也即不支持排序。

3)哈希表处理冲突的机制本身可能就是一个缺陷,攻击者可以通过精心构造数据,来实现处理冲突的最坏情况。即:每次都出现冲突,甚至每次都出现多次冲突,以此来大幅度降低哈希表的性能。这种攻击也被称为基于哈希冲突的拒绝服务攻击(Hashtable collisions as DOSattack)

4)好的哈希函数(good hash function)的计算成本有可能会显著高于线性表或者搜索树在查找时的内部循环成本,所以当数据量非常小的时候,哈希表是低效的。


参考博客:http://www.cnblogs.com/dolphin0520/archive/2012/09/28/2700000.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值