zoukankan      html  css  js  c++  java
  • 散列


    散列表用途广泛,介绍几个应用案例:
    1 将散列表用于查找

    2 防止重复

    3 将散列表用作缓存(redis/memcache数据库都是作为缓存的!)

    冲突
           大多数语言都提供了散列表实现,你不用知道如何实现它们。 但你依然需要考虑性能!要明白散列表的性能,你得先搞清楚
    什么是冲突。 

    冲突(collision):给两个键分配的位置相同(映射到同一个数字上了)。 

    处理冲突的方式很多,最简单的办法如下:如果两个键映射到了同一个位置,就在这个位置存储一个链表。 所谓的  拉链法

        下面的两个key :apples, Avocados 都映射到 同一个位置了

                  apple和avocado映射到了同一个位置,因此在这个位置存储一个链表。在需要查询香蕉的价格时,速度依然很快。但在需要查询苹果的价格时,速度要慢些:你必须在相应的链表中找到apple。如果这个链表很短,也没什么大不了——只需搜索三四个元素。但是,假设你工作的杂货店只销售名称以字母A打头的商品。

               等等!除第一个位置外,整个散列表都是空的,而第一个位置包含一个很长的列表!换言之,这个散列表中的所有元素都在这个链表中,这与一开始就将所有元素存储到一个链表中一样糟糕:散列表的速度会很慢。

               听我们服务器Java程序同事说, 他们Java字典中的数据结构是  哈希表 + 红黑树,如果冲突的链表的长度超过8个的时候会转为红黑树(它的查询,插入删除操作都是logn).

    这里的经验教训有两个。

     散列函数很重要。前面的散列函数将所有的键都映射到一个位置,而最理想的情况是,散列函数将键均匀地映射到散列表的不同位置。 

     如果散列表存储的链表很长,散列表的速度将急剧下降。然而,如果使用的散列函数很好,这些链表就不会很长!散列函数很重要,好的散列函数很少导致冲突。那么,如何选择好的散列函数呢? 

    要避免冲突,需要有:

     较低的填装因子;

     良好的散列函数。

    1 填装因子

             散列表的填装因子很容易计算。散列表使用数组来存储数据,因此你需要计算数组中被占用的位置数。例如,下述散列表的填装因子为2/5,即0.4。

    下面这个散列表的填装因子为多少呢?

    如果你的答案为1/3,那就对了。填装因子度量的是散列表中有多少位置是空的。

                假设你要在散列表中存储100种商品的价格,而该散列表包含100个位置。那么在最佳情况下,每个商品都将有自己的位置。

              这个散列表的填装因子为1。如果这个散列表只有50个位置呢?填充因子将为2。不可能让每种商品都有自己的位置,因为没有足够的位置!填装因子大于1意味着商品数量超过了数组的位置数。一旦填装因子开始增大,你就需要在散列表中添加位置,这被称为调整长度(resizing)。例如,假设有一个像下面这样相当满的散列表。

    你就需要调整它的长度。为此,你首先创建一个更长的新数组:通常将数组增长一倍。

                   接下来,你需要使用函数hash将所有的元素都插入到这个新的散列表中。

            这个新散列表的填装因子为3/8,比原来低多了!填装因子越低,发生冲突的可能性越小,散列表的性能越高。一个不错的经验规则是:一旦填装因子大于0.7,就调整散列表的长度。你可能在想,调整散列表长度的工作需要很长时间!你说得没错,调整长度的开销很大,因此你不会希望频繁地这样做。但平均而言,即便考虑到调整长度所需的时间,散列表操作所需的时间也为O(1)。

    2 良好的散列函数

                良好的散列函数让数组中的值呈均匀分布。

    糟糕的散列函数让值扎堆,导致大量的冲突。

            什么样的散列函数是良好的呢?   如果你好奇,可研究一下SHA函数 。你可将它用作散列函数。 

                  比如在版本控制git中就用到了hash:在git中,文件内容为键值,并用SHA算法作为hash function,将文件内容对应为固定长度的字符串(hash值)。如果文件内容发生变化,那么所对应的字符串就会发生变化。git通过比较较短的hash值,就可以知道文件内容是否发生变动。

                再比如计算机的登陆密码,一般是一串字符。然而,为了安全起见,计算机不会直接保存该字符串,而是保存该字符串的hash值(使用MD5、SHA或者其他算法作为hash函数)。当用户下次登陆的时候,输入密码字符串。如果该密码字符串的hash值与保存的hash值一致,那么就认为用户输入了正确的密码。这样,就算黑客闯入了数据库中的密码记录,他能看到的也只是密码的hash值。上面所使用的hash函数有很好的单向性:很难从hash值去推测键值。因此,黑客无法获知用户的密码。

    还有两种解决冲突的方法:

              a)开放地址法
    开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,...,k(k<=m-1)
    其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。如果di值可能为1,2,3,...m-1,称线性探测再散列。
    如果di取1,则每次冲突之后,向后移动1个位置.如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,...k*k,-k*k(k<=m/2) 
    称二次探测再散列。如果di取值可能为伪随机数列。称伪随机探测再散列。仍然以学生排号作为例子,
    现有两名同学,李四,吴用。李四与吴用事先已排好序,现新来一名同学,名字叫王五,对它进行编制

    10.. .... 22 .. .. 25
    李四.. .... 吴用 .. .. 25
       赵刚未来之前

    10.. .. 22 23 25
    李四..   吴用 王五  
       (a)线性探测再散列对赵刚进行编址,且di=1

    10... 20 22 .. 25
    李四.. 王五 吴用    
       (b)二次探测再散列,且di=-2 

    1... 10... 22 .. 25
    王五.. 李四.. 吴用    
       (c)伪随机探测再散列,伪随机序列为:5,3,2 

               b)再哈希法 
    当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。
    比如上面第一次按照姓首字母进行哈希,如果产生冲突可以按照姓字母首字母第二位进行哈希,再冲突,第三位,直到不冲突为止

    就这三种方法!

     
    ————————————————
    版权声明:本文为CSDN博主「u010019717」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u010019717/article/details/80551498

  • 相关阅读:
    eclipse中不能识别enum
    The source attachment does not contain the source for the file Activity.class
    ASP.NET应用程序脱机问题
    鱼仔系统部署教程
    鱼仔系统开发教程
    mysql innodb cluster 无感知集群
    Mysql 8.0 新特性测试
    随笔1
    音频输出格式
    2012.02.03(S3C6410中文手册笔记)(一)
  • 原文地址:https://www.cnblogs.com/kakaisgood/p/12133755.html
Copyright © 2011-2022 走看看