数据结构之查找(七)——散列表(哈希表)

基本概念

  • 基本思想:记录的存储位置与关键字之间存在对应关系,这种对应关系通过hash函数来体现, L o c ( i ) = H ( k e y i ) Loc(i)=H(keyi) Loc(i)=H(keyi),其中H(keyi)为hash函数。

  • 例子

    • 例子1
      在这里插入图片描述
      上图中,学生学号的存储位置为学号的后两位。
    • 例子2
      在这里插入图片描述
  • 如何查找?
    以上两例为例,
    若查找2001011810216的信息,可直接访问V[16];
    查找key=9,则访问H(9)=9号地址,若内容为9则成功,若查不到,则返回空指针或空记录。

  • 优缺点

    • 优点:查找效率高
    • 缺点:空间效率低
  • 相关术语:

    • 散列方法: 选取某个函数,依该函数按关键字计算元素存储位置,并按此存放;查找时,由同一函数对给定值k计算地址,将k与地址单元中元素关键码进行对比,确定查找是否成功。
    • 散列函数: 散列方法中使用的转换函数
    • 散列表: 按上述思想构造的表。
    • 冲突: 不同的关键码映射到同一个散列地址, k e y 1 ≠ k e y 2 , 但 是 H ( k e y 1 ) = H ( k e y 2 ) key1 \ne key2,但是H(key1) = H(key2) key1̸=key2,H(key1)=H(key2)
      在这里插入图片描述
    • 同义词: 具有相同函数值的多个关键字,如上图中的39,25和11。


构造方法

  • 使用散列表要解决好两个问题:

    • 构造好的散列函数
      • 所选函数尽可能简单,以便提高转换速度;
      • 所选函数对关键码计算出的地址,应在散列地址集中致均匀分布,以减少空间浪费。
    • 制定一个好的解决冲突的方案:
      • 查找时,如果从散列函数计算出的地址中查不到关键码,则应当依据解决冲突的规则,有规律地查询其它相关单元。
  • 构造散列函数需考虑的因素:

    • 执行速度(计算散列函数所需时间)
    • 关键字长度
    • 散列表大小
    • 关键字的分布情况
    • 查找频率
  • 根据元素集合的特性构造:

    • 要求1:n个数据原来仅占用n个地址,虽然散列查找是以空间换时间,但仍希望散列的地址空间尽量小
    • 要求2:无论用什么方法存储,目的都是尽量均匀地存放元素,以避免冲突。
  • 常用的方法有:

    • 直接定址法
    • 数字分析法
    • 平方取中法
    • 折叠法
    • 除留余数法
    • 随机数法

下面重点介绍直接定址法和除留余数法。

  • 直接定值法:
    H a s h ( k e y ) = a ⋅ k e y + b ( a 、 b 为 常 数 ) Hash(key) = a \cdot key + b(a、b为常数) Hash(key)=akey+b(ab)
    优点:以关键码key的某个线性函数值为散列地址,不会产生冲突。
    缺点:要占用连续地址空间,空间效率低。
    在这里插入图片描述

  • 除留余数法:
    H a s h ( k e y ) = k e y     m o d     p    ( p 是 一 个 整 数 ) Hash(key) = key\;\bmod \;p\;(p是一个整数) Hash(key)=keymodp(p)

    • 选取合适p的技巧:若表长为m,取 p ≤ m p \le m pm且为质数。
      在这里插入图片描述


处理冲突的方法

  • 常用方法有:
    • 开放地址法(开地址法)
    • 链地址法(拉链法)
    • 再散列法(双散列函数法)
    • 建立一个公共溢出区

下面重点介绍开地址法和链地址法。

  • 开地址法

    • 基本思想:有冲突时就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将数据元素存入。
    • 例如:除留余数法
      H i = ( H a s h ( k e y ) + d i )     m o d     m            d i 为 增 量 序 列 {H_i} = (Hash(key) + {d_i})\;\bmod \;m\;\;\;\;\;{d_i为增量序列} Hi=(Hash(key)+di)modmdi
    • 根据 d i d_i di构造方法的不同,将开地址法又分为3种,即,
      • 线性探测法 d i 为 1 , 2 , ⋯   , m − 1 线 性 序 列 {d_i}为1,2, \cdots ,m - 1线性序列 di1,2,,m1线
      • 二次探测法 d i 为 1 2 , − 1 2 , 2 2 , − 2 2 , ⋯   , q 2 二 次 序 列 {d_i}为{1^2}, - {1^2},{2^2}, - {2^2}, \cdots ,{q^2}二次序列 di12,12,22,22,,q2
      • 伪随机探测法 d i 为 伪 随 机 序 列 d_i为伪随机序列 di
  • 开地址法——线性探测法

    • H i = ( H a s h ( k e y ) + d i ) &ThickSpace;&VeryThinSpace; m o d &VeryThinSpace;&ThickSpace; m &ThickSpace;&ThickSpace;&ThickSpace;&ThickSpace;&ThickSpace; ( 1 ≤ i &lt; m ) {H_i} = (Hash(key) + {d_i})\;\bmod \;m\;\;\;\;\;(1\le i&lt;m) Hi=(Hash(key)+di)modm(1i<m)
      其中,m为散列表长度, d i d_i di为递增序列 1 , 2 , ⋯ &ThinSpace; , m − 1 , 且 d i = i 1,2,\cdots,m-1,且d_i=i 1,2,,m1,di=i
      一旦有冲突,就找下一个地址,直到找到空地址存入。
    • 例子: 关键码集为{47,7,29,11,16,92,22,8,30},散列表的表长为m=11;散列函数为 H a s h ( k e y ) = k e y &ThickSpace; m o d &ThickSpace; 11 Hash(key)=key\;mod\;11 Hash(key)=keymod11;拟用线性探测法解决冲突。
      在这里插入图片描述
  • 开地址法——二次探测法
    在这里插入图片描述

  • 开地址法——伪随机探测法

    • H i = ( H a s h ( k e y ) + d i ) &ThickSpace;&VeryThinSpace; m o d &VeryThinSpace;&ThickSpace; m &ThickSpace;&ThickSpace;&ThickSpace;&ThickSpace;&ThickSpace; ( 1 ≤ i &lt; m ) {H_i} = (Hash(key) + {d_i})\;\bmod \;m\;\;\;\;\;(1\le i&lt;m) Hi=(Hash(key)+di)modm(1i<m)
      其中:m为散列表长度, d i d_i di为伪随机数。
  • 链地址法

    • 基本思想: 相同散列地址的记录链成一单链表,m个散列地址就设m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构。
    • 例子: 一组关键字为{19,14,23,1,68,20,84,27,55,11,10,79},取散列函数为 H a s h ( k e y ) = k e y &ThickSpace; m o d &ThickSpace; 13 Hash(key)=key\;mod\;13 Hash(key)=keymod13
      方法如下图,
      在这里插入图片描述
    • 具体步骤:
      • Step1: 取数据元素的关键字key,计算其散列函数值(地址)。若该地址对应的链表为空,则将该元素插入此链表;否则执行Step2解决冲突。
      • Step2: 根据选择的冲突处理方法,计算关键字key的下一个存储地址。若该地址对应的链表不为空,则利用链表前插法或后插法将该元素插入此链表。
    • 优点:
      • 非同义词不会冲突,无“聚集”现象。
      • 链表上结点空间动态申请,更适合于表长不确定的情况。


散列表的查找

  • 查找过程:
    在这里插入图片描述

  • 例子: 已知一组关键字为{19,14,23,1,68,20,84,27,55,11,10,79},取散列函数为 H a s h ( k e y ) = k e y &ThickSpace; m o d &ThickSpace; 13 Hash(key)=key\;mod\;13 Hash(key)=keymod13 ,散列表长度为m=16,设每个记录的查找概率相等。

    • 方法1: 线性探测法
      在这里插入图片描述
      A S L = ( 1 ∗ 6 + 2 + 3 ∗ 3 + 4 + 9 ) / 12 = 2.5 ASL=(1*6+2+3*3+4+9)/12=2.5 ASL=(16+2+33+4+9)/12=2.5
    • 方法2: 链地址法
      在这里插入图片描述
      A S L = ( 1 ∗ 6 + 2 ∗ 4 + 3 + 4 ) / 12 = 1.75 ASL=(1*6+2*4+3+4)/12=1.75 ASL=(16+24+3+4)/12=1.75
  • 性能分析:

    • ASL取决于

      • 散列函数
      • 处理冲突的方法
      • 散列表的装填因子 α \alpha α , α = 表 中 填 入 的 记 录 数 哈 希 表 的 长 度 \alpha = \frac{{表中填入的记录数}}{{哈希表的长度}} α=
        α \alpha α越大,表中记录数越多,说明表装得越满,发生冲突得可能性就越大,查找时比较次数就越多。
    • ASL与装填因子 α \alpha α有关,既不是严格的 O ( 1 ) O(1) O(1),也不是严格的 O ( n ) O(n) O(n)

      • 拉链法: A S L ≈ 1 + α 2 ASL \approx 1 + \frac{\alpha }{2} ASL1+2α
      • 线性探测法: A S L ≈ 1 2 ( 1 + 1 1 − α ) ASL \approx \frac{1}{2}(1 + \frac{1}{{1 - \alpha }}) ASL21(1+1α1)
      • 随机探测法: A S L ≈ − 1 α ln ⁡ ( 1 − α ) ASL \approx - \frac{1}{\alpha }\ln (1 - \alpha ) ASLα1ln(1α)
  • 几点结论:

    • 散列表技术具有很好的平均性能,优于一些传统的技术。
    • 链地址法优于开地址法。
    • 除留余数法作散列函数优于其他类型函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值