【数据结构(34)】7.4 散列表的查找

一、散列表的基本概念

  • 基本思想:根据要存储的关键字的值,来计算该存在哪里。
    • 对应关系 —— hash 函数,通过这个函数将关键字的值对应到它的存储位置。
    • Loc(i) = H(keyi)。

在这里插入图片描述

举个例子

【例1】

在这里插入图片描述
在这里插入图片描述

  • 这些同学们的信息,既不是按照输入顺序存的,也没有排好序按照递增或递减的方式存储。
  • 而是根据学号最后两位数,直接对应到存储位置。
    • 如果想找某位同学的信息,直接根据学号后两位去对应位置找就行了。

【例2】

  • 根据元素序列(21,23,39,9,25,11),若规定每个元素 k 的存储地址 H(k) = k,请画出存储结构图。
    • H(k) = k:关键字的值是多少,对应的位置就是多少。
  • 按照这样的方式来存储的话,要找某一个元素就会非常方便了,直接根据给定的值去固定的位置找就行了。

在这里插入图片描述

如何查找

在这里插入图片描述

  • 根据散列函数:H(key) = k
    • 查找 key = 9,则访问 H(9) = 9 号地址,若内容为 9 则成功找到;
    • 若查不到,则返回一个特殊值,如空指针或空记录。

散列表的特点

  • 优点查找效率高,时间效率可以达到 O(1)。
  • 缺点空间效率低,就像上面那六个元素占了39个空间。

散列表的术语

  1. 散列方法(杂凑法):
    • 选取某个函数,依该函数按关键字计算元素的存储位置,并按此存放;
    • 查找时,由同一个函数对给定值 k 计算地址。将 k 与地址单元中元素关键码进行比较,来确定查找是否成功。
  2. 散列函数(杂凑函数):散列方法中使用的转换函数
  3. 散列表(杂凑表):
    • 一个有限连续的存储空间,用来存储按照散列函数计算得到相应散列地址的数据记录。
    • 通常散列表的存储空间是一个一维数组,散列地址是数组下标。

在这里插入图片描述

  1. 冲突:不同的关键码映射到同一个散列地址。
    • key1 ≠ key2,但是 H(key1) = H(key2)。值不相同,但是都住进了一间房。
    • 例:有 6 个元素的关键码分别为:(25,21,39,9,23,11)。
      • 选取关键码与元素位置间的函数为 H(k) = k % 7.
      • 地址编号从 0 - 6.。
    • 在散列查找方法中,冲突是不可避免的,只能尽可能减少。

在这里插入图片描述

  1. 同义词:具有相同函数值的多个关键字。
    • 如:上图互相冲突的值,虽然值不相同,但是函数值一样,导致了它们会呆在同一块空间内。

在这里插入图片描述

二、散列函数的构造

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

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

构造散列函数需要考虑的因素

  1. 执行速度:即计算散列函数所需要的时间;
  2. 关键字的长度
  3. 散列表的大小:散列表越大,产生冲突的可能性越小,但是浪费空间;
  4. 关键字的分布情况:根据关键字的特点,怎样才能使他们分布的更均匀;
  5. 查找频率:让需要经常查找的元素更容易被找到。

根据元素集合的特性构造

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

1. 散列函数的构造方法

  1. 直接定址法
  2. 数字分析法
  3. 平方取中法
  4. 折叠法
  5. 除留余数法
  6. 随机数法

直接定址法

  • Hash(key) = a * key + b(a、b为常数)

  • 优点:以关键字 key 的某个线性函数值为散列地址,不会产生冲突。

  • 缺点:要占用连续地址空间,空间效率低。

    • 例:{100,300,500,700,800,900},
      散列函数:Hash(key) = key/100(a = 1/100,b = 0)

在这里插入图片描述

除留余数法

  • Hash(key) = key % p(p 是一个常数),将余数作为数据元素的存储位置。

  • 关键:如何选取合适的 p ?

  • 技巧:假设表长为 m,取 p <= m 且为质数

    • 例:{15, 23, 27, 38, 53, 61, 70},散列函数 Hash(key) = key % 7,7 <= 表长7,且为质数。

在这里插入图片描述

  • 如果想反过来找某个数也是同样的方法,如:找 61,61 % 7 = 5,那么就去 5 号位置找它。

三、处理冲突的方法

  1. 开放定址法(开地址法)
  2. 链地址法(拉链法)
  3. 再散列法(双散列函数法)
  4. 建立一个公共溢出区

1. 开地址法

基本思想

  • 有冲突时就去寻找下一个空的散列地址;
  • 只要散列表足够大,空的散列地址总能找到,并将数据元素存入。
    • 例如:除留余数法 Hi = (Hash(key) + di) % m,di 为增量序列。
  • 开地址法的三种方法 % 的都是表长 m ,不要和除留余数法构造散列表的 % 最大质数 p 搞混了。

常用方法

  • 线性探测法:增量序列 di 为 1,2,…m-1 的这样一个线性序列。
  • 二次探测法:增量序列 di 为 12,-12,22,-22,…,q2 的二次序列。
  • 伪随机探测法:增量序列 di 为伪随机数序列,
    • 加上一个伪随机数,将 key 随机的存储到后面的某一个位置,这个位置空着的话就存上,非空的话就继续产生一个随机数找下一块空间。

1.1 线性探测法

  • Hi = (Hash(key) + di) % m(1 <= i < m)
    • 其中:m 为散列表长度,di 为增量序列 1,2,…m-1,且 di = i
    • 一旦冲突,就找下一个地址,直到找到空地址存入

举个例子

  • 关键码集合为 {47,7,29,11,16,92,22,8,3},散列表长度为 m = 11;散列函数为 Hash(key) = key % 11;拟用线性探测法来来处理冲突。

在这里插入图片描述

  1. 47 % 11 = 3,将 47 放在 3 号位置。7 % 11 = 7,将 7 放在 7 号位置。

在这里插入图片描述

  1. Hash(29) = 29 % 11 = 7,此时出现冲突,需要存到下一位置。
    • di = 1,2,3,…,m-1,由 H₁ = (Hash(29 + 1) % 11 = 8,并且散列地址 8 还空着,因此将 29 存入。
    • 让 29 存进去的时候已经做了两次运算了。

在这里插入图片描述

  1. Hash(11) = 11 % 11 = 0,且 0 还空着,将 11 存入 0号位置。Hash(16) = 16 % 11= 5,且 5 为空,将 16 存入 5号位置。Hash(92) = 92 % 11 = 4,4 空,存之。

在这里插入图片描述

  1. Hash(22) = 0,此时 22 与 0 号位置的 11 产生冲突。
    • 往后移一位 (Hash(22) + 1) % 11 = 1,存储到 1 号位置去。

在这里插入图片描述

  1. Hash(8) = 8,与 29 产生冲突,寻找下一块空置空间 9 存入。

在这里插入图片描述

  1. Hash(3) = 3,与 47 冲突,依次往后寻找闲置空间。
    • (Hash(3) + 3) % 11 = 6,di 从 1 一直加到 3 才找到一块空着的空间 6,此时将 3 存入。

在这里插入图片描述

  • 同样,如果想要找出 3 的话,Hash(3) = 3 % 11 = 3,先从 3 号位置开始找,发现不是则依次往后找,直到比较了 4 次之后才终于找到。

平均查找长度

  • 将所有元素的比较次数相加 / 元素个数
    • 比较次数:11 要比较一次,22 比较两次,3 比较3次…

在这里插入图片描述

1.2 二次探测法

  • 关键码集合为 {47,7,29,11,16,92,22,8,3},
  • 设:散列函数为 Hash(key) = key % 11Hi = (Hash(key) + di) % m
  • 其中:m 为散列表长度, m 要求是某个 4k + 3 的质数;di 为增量序列 12,-12,22,-22,…,q2
    • 如果当前要插入的值与当前位置产生冲突,首先要探测当前位置的下一位置,如果下一位置还是冲突,则探测当前位置的前一位置。

举个例子

在这里插入图片描述

  • 其中:

    • 黑色数字为第一次就找到空闲位置成功存进去的值。
    • 蓝色为第一次比较时与其他位置有冲突的值,它们可以直接找到下一位置作为栖息地。
  • 剩下的元素 3 就不能只通过往后移一位找到地方待了。

    • 3 号位置已经有元素了,先让 3 在当前 3 号位置上加 12 看看 4 是否有空位,4 号位置非空,此时应该移动 -12 位到 2 号位置判断是否有空位,2 号位置为空,将关键码 3 插入在此。

在这里插入图片描述

2. 链地址法

基本思想

  • 将相同散列地址的记录链成一条单链表,m 个散列地址就设置 m 个单链表,然后用一个数组将 m 个单链表的表头指针存储起来,形成一个动态的结构。

举个例子

  • 现有一组关键字为: {19,14,23,1,68,20,84,27,55,11,10,79}
  • 散列函数为:Hash(key) = key % 13
    • 这个时候我们就发现了, 有几个元素是同义词(% 13 的余数相等),如14,1,27,79,也就是说这几个元素会产生冲突。
    • 将同义词(冲突的元素)链接在同一条单链表上。
    • 将链表存储在函数值算出来的位置上,如 14,1,27,79,他们的余数都是 1,将由他们链接的链表首地址放在数组 1 号位置,其余元素同理。

在这里插入图片描述

链地址法建立散列表步骤

  • 步骤1:取数据元素的关键字 key,计算其散列函数值(地址)。
    • 若该地址对应的链表为空,则将该元素插入此链表;否则执行 Step2 解决冲突。
  • 步骤2:根据选择的冲突处理方法,计算关键字 key 的下一个存储地址。
    • 若该地址对应的链表不为空,则利用链表的前插法或后插法将该元素插入此链表。

链地址法的优点

  • 非同义词不会冲突,无聚集现象。
  • 链表上结点空间动态申请,更适合于表长不确定的情况。

四、散列表的查找

算法步骤

  1. 给定待查找的关键字 key,根据造表时设定的散列函数计算 Ho = H(key)。
  2. 若单元 Ho 为空,则所查找元素不存在。
  3. 若单元 Ho 中元素的关键字为 key,则查找成功。
  4. 否则重复以下解决冲突的过程:
    • 按处理冲突的方法,计算下一个散列地址 Hi;
    • 若单元 Hi 为空,则所查找元素不存在;
    • 若单元 Hi 中元素的关键字为 key,则查找成功。

在这里插入图片描述

举个例子

  • 已知一组关键字(19,14,23,1,68,20,84,27,55,11,10,79)散列函数为:H(key) = key % 13,散列表长为 m = 16,假设每个记录的查找概率相等。

一、线性探测然后再散列处理冲突,即 Hi = (H(key) + di) % m

  • 第一次构造散列表是 % 小于表长的最大质数,所以是13。
  • 处理冲突的时候是要 mod 表长,所以是 % 16.

如果想要找 79 的话就需要比较 9 次才能找到。在这里插入图片描述
在这里插入图片描述

  • 平均查找长度:所有元素的比较次数之和 / 元素个数。
    • ASL = (1 X 6 + 2 + 3 X 3 + 4 + 9)/ 12 = 2.5

二、用链地址法处理冲突

在这里插入图片描述

1. 散列表的查找效率分析

使用平均查找长度 ASL 来衡量查找算法,ASL 取决于

  1. 散列函数
  2. 处理冲突的方法
  3. 散列表的装填因子 α
    • α 越大,表中记录数越多,说明表装的越满,发生冲突的可能性就越大,查找时比较次数就越多。

在这里插入图片描述

  • ASL 与装填因子 α 有关!既不是严格的 O(1),也不是 O(n)。

在这里插入图片描述

总结

  • 散列表技术机油很好的平均性能,优于一些传统的技术。
  • 链地址法优于开地址法:
    • 查找效率;
    • 动态开辟内存空间。
  • 除留余数法作散列函数优于其他类型函数。
  • 13
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值