【细谈数据结构】最最最详细的散列表(哈希表)讲解!!!(二)

细谈散列表系列一共有三篇文章

1、散列表的概述
2、散列函数的作用与构造
3、散列表查找的代码实现

上篇文章我们提到,散列函数在存储时会发生冲突,并且也解释了冲突是如何产生的。

但我们先不着急去解决冲突的问题,我们首先来补充一个概念,散列表是基于散列函数建立的一种查找表。

那散列函数在查找中,具体是干什么的呢?

别急,本章内容就带你了解散列函数的作用以及构造


1、查找时的:散列函数?

  1. 散列函数就是根据key 计算出应该存储位置的位置
  2. 根据这个函数和查找关键字key,可以直接确定查找值所在位置
  3. 地址index = f(key)

简单来说就是,存哪去?存哪了?


2、散列函数的构造方法

散列函数的构造方法一共有六种,并且每种方法的适应场景都不一样。

1、直接定址法

  • 取关键字的某个线性函数值为散列地址
    • 如:f(key) = a * key + b
  • 优点:简便、均匀,也不会产生冲突
  • 缺点:仅限于地址大小 = 关键字集合的情况
    • 简单,但不常用
    • 需要事先知道关键字的分布情况
    • 适合查找表较小且连续的情况

2、数字分析法

  • 抽取
    • 抽取方法是使用关键字的一部分来计算散列存储位置的方法。
    • 散列函数中常常用到的手段
  • 假设关键字集合中的每个关键字key 都由s 位数字组成(k1, k2, k3, ···,kn),分析key 中的全体数据,并从中提取分布均匀的若干位他们的组合构成全体
    • 分布均匀的意思是,不容易重复或冲突。
  • 通常用于处理关键字位数较长的情况

如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。

在这里插入图片描述

3、平方取中法

  • 关键字的每一位都有某些数字重复出现频率很高的现象,可以先求关键字的平方值,通过平方扩大差异,而后取中间数作为最终存储地址

适合于不知道关键字的分布,而位数又不是很大的情况。

4、折叠法

  • 将关键字从左到右分割成位数相等的几部分
    • 最后一部分位数不够可以短些
    • 将这几部分叠加求和
    • 并按照散列表表长,取后几位作为散列地址
  • 有时这可能还不能够保证分布均匀,不妨从一端向另外一端来回折叠后对齐相加。

事先不需要知道关键字的分布,适合关键字位数较多的情况。

5、除留余数法

  • 常用的构造散列函数方法
    • H(key)= key MOD p
      • p <= m
      • m 为表长
  • 如何选取p 为关键
    • 本方法的关键在于选取合适的p,p如果选的不好,可能会容易产生同义词
    • p 应为小于等于m,最好是接近m 的最小质数
      • 质数:大于1的自然数,并且只能被1和本身整除
    • 或是不包含小于20 质因子的合数
      • 质因子:质数的因子
      • 合数: 整数中除了能被1和本身整除外,还能被0除外的其他数整除的数
      • 合数的性质:
        • 1、所有大于2的偶数都是合数
        • 2、所有大于5的奇数中,个位为5的都是合数
        • 3、除0以外,所有个位为0的自然数都是合数

这样可以减少地址的重复(冲突)

6、随机数法

  • 选择一个随机数,取关键字的随机函数值为它的散列地址
  • H(key)= Random(key)
    • random 为随机函数

关键字的长度不等,采用该方法比较合适


3、采用散列函数的参考因素:

既然有那么多种方法,那我具体该用哪一种呢?

选择困难症发作······

别着急,我这里有一份参考标准,你只要综合这些因素,就能决策选择哪种散列函数更合适了:

  1. 计算散列地址所需的时间
  2. 关键字的长度
  3. 散列表的大小
  4. 关键字的分布情况:关键字分布是否均匀,是否有规律可循
  5. 记录查找的频率

设计的散列函数在满足以上条件的情况下尽量减少冲突


4、解决散列冲突:

讲了这么久的散列函数结构,那么我们接下来的重头戏来了,就算你散列函数设计的再好,但是数据那么多,你难免会遇到冲突。

当我们遇到冲突的时候,程序又不是像你一样,聪明机灵,会懂得去找别的地址。他只是一个憨憨的愣头青,遇到困难只会傻傻的停在那里。所以我们需要为他规划Plan B

散列表冲突的解决方法有四种:

  1. 开方定址法
  2. 再散列函数法
  3. 链地址法
  4. 公共溢出区法

接下来我们来详细的讲解四种方法:

1、开方定制法

一旦发生冲突,就去寻找下一个空的散列地址

  • 只要散列表足够大,空的散列地址总能找到,并将记录存入
  • fi( key) = ( f ( key ) + di ) MOD m
    • di = 1, 2, 3, ···, m - 1

例子:

  • 散列函数f( key ) = key mod 12
  • key = 37 时,发现f ( 37 ) = 1,与25 所在位置发生冲突
  • 采用上面公式
    • f ( 37) = ( f ( 37 ) + 1 ) mod 12 = 2
    • 于是将37 存入下标为2 的位置

解决冲突的三种方法:

  1. 线性探测法

    • 通过不断的增大di 的值来寻找空的散列地址:di = di++
    • 堆积
      • 如果碰到48 和 37 这种本来不是同义词却需要争夺一个地址的情况,称为堆积
      • 堆积的出现,使得我们需要不断处理冲突,无论是存入还是查找效率都会大大降低
  2. 二次探测法

    • 线性是不断的向后探索,但是如果它前面还有一个空位置,可是我们却不断向后求,虽然也能得出结果,但是效率很差。
    • 所以我们可以采用双向寻找到可能的位置。
    • 二次探测法就是:增加平方运算的目的是为了不让关键字都聚集在某一块区域
      • fi ( key ) = ( f ( key ) + di ) MOD m
      • di = 12, -12, 22, -22 ···
  3. 随机探测法

    • di 是一组伪随机数列
    • 在冲突时,对于位移量di 采用随机函数计算得到
      • 这里使用的随机函数是伪随机函数
      • 因为使用的随机种子是相同的,所以不断调用随机函数可以生成不会重复的数列。
    • 查找
      • 若我们的随机种子是相同的,每次得到的数列也是相同的,相同的di 当然可以得到相同的散列地址
      • fi ( key ) = ( f ( key ) + di ) MOD m
        • di 是一个随机数列

总之,开方定址法只要在散列表未填满时,总能找到不发生冲突的地址。是我们常用的解决冲突的方法。


2、再散列函数法

  • 准备若干个散列函数,若该散列函数冲突,则使用下一个
    • fi ( key ) = RHi ( key )
      • i = 1, 2, ···, k
      • RHi:就是不同的散列函数

这种方法使得关键字不产生聚集,当然,相应地也增加了计算的时间。


3、链地址法

  • 不使用其他空间,直接在原地解决冲突
  • 在散列表中只存储所有同义词子表的头指针
    • 同义词子表
      • 将所有关键字为同义词的记录存储在一个单链表中
  • 优点:有效的处理了冲突,为其提供了绝不会找不到地址的保障
  • 缺点:查找时需要遍历单链表的性能损耗

在这里插入图片描述

4、公共溢出区法

  • 建立一个特殊存储空间,专门存放冲突的数据
  • 在查找时
    • 对给定值通过散列函数计算出散列地址后
    • 先于基本表的相应位置进行对比
      • 相等,查找成功
      • 不相等,到溢出表里进行顺序查找

适用于数据和冲突较少的情况

在这里插入图片描述


既然我们已经解决了冲突的问题,那么现在就最后补充三个关于查找的知识点。

5、散列表的查找

查找过程和造表过程一致

假设采用开放地址法处理冲突,查找过程为:

  1. 对于给定的key,计算hash 地址index = f(key)
  2. 如果数组 arr[index] 的值为空,则查找不成功
  3. 如果数组 arr[index] == key,则查找成功
    否则,使用冲突解决方法求下一个地址,直到
    arr[index] == key or arr[index] == null

6、散列表查找算法实现

  • 首先是需要定义一个散列表的结构以及一些相关的常数
    • HashTable:散列表结构
    • 结构中的elem:动态数组

7、散列表的查找效率

决定散列表查找的ASL 因素

  1. 用的散列函数
  2. 选用的处理冲突的方法
  3. 散列表的饱和度,装载因子α = n / m
    • n 表示实际装载数据长度
    • m 为表长

一般情况下,假设散列函数是均匀的,则在讨论ASL 时可以不考虑它的因素。

散列的ASL 是处理冲突方法和装载因子的函数


散列表的理论到这里就结束了,接下来的那篇,也是我们散列表系列的最后一篇,会具体讲讲散列表如何实现。

以上就是本篇文章的所有内容了,如果觉得有帮助到你的话,
麻烦动动小手,点赞收藏转发!!!
你的每一次点赞都是我更新的最大动力~

我们下期再见!

猜你喜欢

转载自blog.csdn.net/weixin_43776724/article/details/112987665