zoukankan      html  css  js  c++  java
  • 介绍哈希函数及解决冲突的方法

    哈希函数又叫散列函数,一个哈希函数的输入域可以是非常大的范围,但是他的输出域是一个固定的范围


    哈希函数的性质:

    1. 典型的哈希函数都拥有无限的输入值域
    2. 输入值相同的时候,输出值也一样
    3. 输入值不一样时,输出值可能一样,也可能不一样
    4. 不同的输入值得到的哈希值,整体均匀的分布在输出域上

    目前应用最为广泛的hash函数是SHA-1和MD5,软件的MD5校验就是这个,用户下载的文件或者软件经过哈希函数生成一个编码,和服务提供者中的编码进行比较,如果是原文件或者原版软件生成的编码一定是一样的,如果下载的文件或者软件损坏,或者软件被篡改了,生成的编码是一定不一样的。

    其实哈希函数有点神奇,他可以把任意输入的一个个字符串变成一个个相同长度的字符串等等……,并可以得到类似于数组的一种结构,哈希表(散列表),对于存在他中的元素的查找操作是非常方便的可以是复杂度为O(1)(想想数组的查找操作)
    以上前三点是哈希函数的基本特点,第4条是判断一个哈希函数优劣的关键,也就是说一组数据经过好的哈希函数进行计算之后,他的每个输出值可能都不会重复,并且均匀的分布在一个范围中,但是在实际应用中,很少能做到这些。不同的输入值经过哈希函数计算后得到了相同的输出值。


    这里说一下抽屉原理(又叫鸽巢原理)鸽巢原理的简单形式:如果n+1个物体被放进n个盒子,那么至少有一个盒子包含两个或者更多的物体。也就是在进行哈希计算后,一定有重复的输出值,造成了冲突。既然造成了冲突,就要解决冲突


    解决冲突的两种方法:开放定址法,链地址法


    1.开放定址法:


    只要经过哈希计算后又冲突,就去寻找下一个空的散列地址,只要这个散列表足够大,就一定可以找到一个合适的地址。但是散列表不能定义太大,会带来内存上的巨大开销,所以散列表大小要设计合理。

    Hi(key) = (H(key)+di) MOD m (di=1,2,3,......,m-1), 其中:H(key)为哈希函数;m为哈希表表长;di为增量序列

    大致说一下原理:给定一组数字,首先选取合适的哈希表的长度,尽量选择合适的长度。之后依次进行计算,每得到一个值,去哈希表中看是否该数字对应的地址为空,如果为空此次地址寻找完毕,探测次数为一,如果不为空,原数据加一再进行计算,每次加一,直到找到地址为空的地方,探测次数为找到空的地址判断了多少次。

    例子:已知一组关键字为(30,11,41,26,15,9),用除余法构造散列函数,用线性探查法解决冲突构造这组关键字的散列表。

    已知数据个数为6个,不如取m=7,构建一个从0~6的一个哈希表(散列表),散列函数为:h(key)=key%7

    步骤:1.每个数对7取余计算,30%7=2,地址为2的位置为空,把30放入2位置,比较次数为1次;1%7=4,4位置为空,放入,比较次数1次;41%7=6,6位置为空放入;26%7=5,位置为空放入;15%7=1,位置为空放入;9%7=2,地址2位置已经放入了30,此地址不为空,就接着向下找,(9+1)%7=3,3的位置为空放入。如果得到的地址还不为空,就接着向下找,直到找到空的地址放进去。


    平均查找长度就是比较次数相加除以元素个数(1+1+2+1+1+1)/6=7/6


    这里是一组其他数据的演示动画点击打开链接

    二次探测散列法:

    如果有一个数正好得到1的位置,可是1的后面没有位置,但是他的前面有位置,这样他需要把表中的每个地方都判断一遍,效率很差。

    因此我们可以改进di = 12, -12, 22, -22,……, q2, -q2 (q <= m/2),这样就等于是可以双向寻找到空位置,可以避免前面的需要判断整个表的情况。

    我们称这种方法为二次探测法。


    开放定址法只要在散列表未填满时,总是可以找到不发生冲突的地址。

    2.链地址法(拉链法)

    和开放定址法的求法类似,只不过当冲突的时候,不再像后查找,而是在当前位置生成一条链,是单链表的结构,按照顺序依次计算就可以。


    还是上面的例子:已知一组关键字为(30,11,41,26,15,9),我们用前面同样的7为除数,进行除留余数法:

    下面的每个元素的存储空间大小都应该是相同的

    与开放定址法相比,链地址法有如下几个优点:

    1. 链地址法处理冲突简单,且无堆积现象,他的数据不会非常密集的出现在一块区域,因此平均查找长度较短;
    2. 由于链地址法中是一个链表的结构,拥有链表的所有特点,各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况,减少对不必要内存的开销;
    3. 在用链地址法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填入散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。

    拉链法的缺点:指针需要额外的空间,所以当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子(a=表中填入的记录数 / 哈希表的长度)变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

    链地址法相比于开放定址法,链地址法一定可以找到一个放入元素的位置,就如开放定址法的表满的时候不能放入元素,而链地址法只是在后面动态申请节点就可以。


    参考:

    http://blog.csdn.net/ywok526/article/details/38540885

    http://www.nowamagic.net/academy/detail/3008060g#

  • 相关阅读:
    LINQ中selectManay操作符(五)
    LINQ中select操作符(四)
    高效并发进阶-白银
    JVM回收算法
    一个类是怎么被JVM执行的
    一纸理解JVM
    单例模式
    深入理解Spring AOP思想
    深入理解Spring IOC工作原理
    HashMap扩容全过程
  • 原文地址:https://www.cnblogs.com/duzhentong/p/7816597.html
Copyright © 2011-2022 走看看