数据结构与算法之美学习笔记:18 | 散列表(上):Word文档中的单词拼写检查功能是如何实现的?

目录

  • 前言
  • 散列思想
  • 散列函数
  • 散列冲突
  • 解答开篇

前言

在这里插入图片描述
本节课程思维导图:
在这里插入图片描述
Word 的单词拼写检查功能,虽然很小但却非常实用。你有没有想过,这个功能是如何实现的呢?其实啊,一点儿都不难。只要你学完今天的内容,散列表(Hash Table)。你就能像微软 Office 的工程师一样,轻松实现这个功能。

散列思想

散列表的英文叫“Hash Table”,我们平时也叫它“哈希表”或者“Hash 表”。你一定也经常听过它,但是你是不是真的理解这种数据结构呢?
散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表。
我用一个例子来解释一下。假如我们有 89 名选手参加学校运动会。为了方便记录成绩,每个选手胸前都会贴上自己的参赛号码。假设校长说,参赛编号不能设置得这么简单,要加上年级、班级这些更详细的信息,所以我们把编号的规则稍微修改了一下,用 6 位数字来表示。比如 051167,其中,前两位 05 表示年级,中间两位 11 表示班级,最后两位还是原来的编号 1 到 89。这个时候我们该如何存储选手信息,才能够支持通过编号来快速查找选手信息呢?
我们可以把这 89 名选手的信息放在数组里。尽管我们不能直接把编号作为数组下标,但我们可以截取参赛编号的后两位作为数组下标,来存取选手信息数据,编号为01 的选手,我们放到数组中下标为 1 的位置;编号为 02 的选手,我们放到数组中下标为 2 的位置。以此类推,编号为 k 的选手放到数组中下标为 k 的位置。

这就是典型的散列思想。其中,参赛选手的编号我们叫做键(key)或者关键字。我们用它来标识一个选手。我们把参赛编号转化为数组下标的映射方法就叫作散列函数(或“Hash 函数”“哈希函数”),而散列函数计算得到的值就叫作散列值(或“Hash 值”“哈希值”)。
在这里插入图片描述
通过这个例子,我们可以总结出这样的规律:散列表用的就是数组支持按照下标随机访问的时候,时间复杂度是 O(1) 的特性。我们通过散列函数把元素的键值映射为下标,然后将数据存储在数组中对应下标的位置。当我们按照键值查询元素时,我们用同样的散列函数,将键值转化数组下标,从对应的数组下标的位置取数据。

散列函数

散列函数,顾名思义,它是一个函数。我们可以把它定义成 hash(key),其中 key 表示元素的键值,hash(key) 的值表示经过散列函数计算得到的散列值。

int hash(String key) {
  // 获取后两位字符
  string lastTwoChars = key.substr(length-2, length);
  // 将后两位字符转换为整数
  int hashValue = convert lastTwoChas to int-type;
  return hashValue;
}

刚刚的散列函数比较简单,也比较容易想到。但是,如果参赛选手的编号是随机生成的 6 位数字,又或者用的是 a 到 z 之间的字符串,该如何构造散列函数呢?我总结了三点散列函数设计的基本要求:

  1. 散列函数计算得到的散列值是一个非负整数;
  2. 如果 key1 = key2,那 hash(key1) == hash(key2);
  3. 如果 key1 ≠ key2,那 hash(key1) ≠ hash(key2)。
    第一点和第二点理解起来比较简单,第三点理解起来可能会有问题。这个要求看起来合情合理,但是在真实的情况下,要想找到一个不同的 key 对应的散列值都不一样的散列函数,几乎是不可能的。即便像业界著名的MD5、SHA、CRC等哈希算法,也无法完全避免这种散列冲突。

散列冲突

再好的散列函数也无法避免散列冲突。那究竟该如何解决散列冲突问题呢?我们常用的散列冲突解决方法有两类,开放寻址法(open addressing)和链表法(chaining)。

  1. 开放寻址法
    开放寻址法的核心思想是,如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入。那如何重新探测新的位置呢?我先讲一个比较简单的探测方法,线性探测(Linear Probing)。当我们往散列表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。
    在这里插入图片描述
    从图中可以看出,散列表的大小为 10,在元素 x 插入散列表之前,已经 6 个元素插入到散列表中。x 经过 Hash 算法之后,被散列到位置下标为 7 的位置,但是这个位置已经有数据了,所以就产生了冲突。于是我们就顺序地往后一个一个找,看有没有空闲的位置,遍历到尾部都没有找到空闲的位置,于是我们再从表头开始找,直到找到空闲位置 2,于是将其插入到这个位置。
    我们通过散列函数求出要查找元素的键值对应的散列值,然后比较数组中下标为散列值的元素和要查找的元素。如果相等,则说明就是我们要找的元素;否则就顺序往后依次查找。如果遍历到数组中的空闲位置,还没有找到,就说明要查找的元素并没有在散列表中。
    在这里插入图片描述
    散列表跟数组一样,不仅支持插入、查找操作,还支持删除操作。对于使用线性探测法解决冲突的散列表,删除操作稍微有些特别。我们不能单纯地把要删除的元素设置为空。我们可以将删除的元素,特殊标记为 deleted。当线性探测查找的时候,遇到标记为 deleted 的空间,并不是停下来,而是继续往下探测。
    在这里插入图片描述
    你可能已经发现了,线性探测法其实存在很大问题。当散列表中插入的数据越来越多时,散列冲突发生的可能性就会越来越大,空闲位置会越来越少,线性探测的时间就会越来越久。
    对于开放寻址冲突解决方法,除了线性探测方法之外,还有另外两种比较经典的探测方法,二次探测(Quadratic probing)和双重散列(Double hashing)。
    所谓二次探测,跟线性探测很像,线性探测每次探测的步长是 1,那它探测的下标序列就是 hash(key)+0,hash(key)+1,hash(key)+2……而二次探测探测的步长就变成了原来的“二次方”,也就是说,它探测的下标序列就是 hash(key)+0,hash(key)+12,hash(key)+22……
    所谓双重散列,意思就是不仅要使用一个散列函数。我们使用一组散列函数 hash1(key),hash2(key),hash3(key)……我们先用第一个散列函数,如果计算得到的存储位置已经被占用,再用第二个散列函数,依次类推,直到找到空闲的存储位置。

不管采用哪种探测方法,当散列表中空闲位置不多的时候,散列冲突的概率就会大大提高。为了尽可能保证散列表的操作效率,一般情况下,我们会尽可能保证散列表中有一定比例的空闲槽位。我们用装载因子(load factor)来表示空位的多少。装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降。
2. 链表法
链表法是一种更加常用的散列冲突解决办法,相比开放寻址法,它要简单很多。我们来看这个图,在散列表中,每个“桶(bucket)”或者“槽(slot)”会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。
在这里插入图片描述
插入的时候,我们只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的时间复杂度是 O(1)。当查找、删除一个元素时,我们同样通过散列函数计算出对应的槽,然后遍历链表查找或者删除。那查找或删除操作的时间复杂度是多少呢?实际上,这两个操作的时间复杂度跟链表的长度 k 成正比,也就是 O(k)。对于散列比较均匀的散列函数来说,理论上讲,k=n/m,其中 n 表示散列中数据的个数,m 表示散列表中“槽”的个数。

解答开篇

Word 文档中单词拼写检查功能是如何实现的?
常用的英文单词有 20 万个左右,假设单词的平均长度是 10 个字母,平均一个单词占用 10 个字节的内存空间,那 20 万英文单词大约占 2MB 的存储空间,就算放大 10 倍也就是 20MB。对于现在的计算机来说,这个大小完全可以放在内存里面。所以我们可以用散列表来存储整个英文单词词典。当用户输入某个英文单词时,我们拿用户输入的单词去散列表中查找。如果查到,则说明拼写正确;如果没有查到,则说明拼写可能有误,给予提示。借助散列表这种数据结构,我们就可以轻松实现快速判断是否存在拼写错误。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/140135.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

EXCEL——计算数据分散程度的相关函数

一、PERCENTIL函数 1.函数介绍 通常用来返回数据集给定百分点上的值。 2.函数解读 函数公式: PERCENTILE(数据, 百分点) 参数释义: 数据(必填):待处理的数组或数据区域。 百分点(必填)&…

mapboxGL中的底图切换

概述 底图切换,这么简单的功能还要写一篇文章?值得的,为什么这么说呢?因为mapboxGL的矢量底图有上百个,不同的底图用的样式、图层的名称、图层的内容、字体库、图标库都不一样,尤其是当地图上已经叠加了很…

2022年06月 Python(五级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 Python中 print(“八进制{: o}”.format(12)) 正确的输出结果是?( ) A: 八进制:O B: 八进制:O14 C: 八进制14O D: 八进制14 答案:D 字符串的format()格式。 第2题 下列的程…

计算机组成原理之概述

概述 计组主要讲的是计算机的硬件实现方式。 机器字长 比如8080处理器,如果想处理16位数的整数运算,就需要执行两次。 可见,机器字长会影响到数据的处理速度。 计算机硬件的基本组成 早期的冯诺依曼机 冯诺依曼提出了“存储程序”的概念&…

汽车FMCW毫米波雷达信号处理流程(推荐---基础详细---清楚的讲解了雷达的过程---强烈推荐)

毫米波雷达在进行多目标检测时,TX发射一个Chirp,在不同距离下RX会接收到多个反射Chirp信号(仅以单个chirp为例)。 雷达通过接收不同物体的发射信号,并转为IF信号,利用傅里叶变换将产生一个具有不同的分离峰…

SQL-----STUDENT

【学生信息表】 【宿舍信息表】 【宿舍分配表】 为了相互关联,我们需要在表中添加外键。在宿舍分配表中添加用于关联学生信息表的外键 student_id,以及用于关联宿舍信息表的外键 dormitory_id; sql代码 -- 创建学生信息表 CREATE TABLE st…

python画折线图 一张图上三条折线 设置折线marker chatgpt画折线图的提示词

chatgpt提示词 用python写一段代码,该代码的功能是:画一个折线图,该折线图x轴的标题是面积,y轴的标题是房价。该图上有三条折线,分别代表深圳,广州,郑州。这三条折线的颜色分别为红&#xff0c…

接口自动化测试流程、工具及其实践!

01、接口自动化测试简介 接口自动化测试是指通过编写脚本或使用自动化工具,对软件系统的接口进行测试的过程。接口测试是软件测试中的一种重要测试类型,主要用于验证系统组件之间的通信和数据交换是否正常。通过接口自动化测试可以快速发现接口中的问题…

飞天使-template模版相关知识

遇到报错django.template.exceptions.TemplateSyntaxError: ‘staticfiles’ is not a registered tag library. Must ROOT_URLCONF TEMPLATES [{BACKEND: django.template.backends.django.DjangoTemplates,DIRS: [os.path.join(BASE_DIR, templates)],APP_DIRS: True,OPTI…

如何设置静态代理IP切换电脑上网地址使用?

在当今的网络时代,代理IP已成为一种常见的网络访问方式。通过使用代理IP,我们可以隐藏自己的真实IP地址,从而保护自己的隐私和安全。但是,有时候我们需要切换代理IP来满足不同的上网需求。本文将介绍如何设置静态代理IP切换电脑上…

【LIUNX】配置DNS服务器

【LIUNX】配置DNS A.安装bind bind-utilsB.修改named.conf配置文件C.生成并修改uos.com.db 文件1.复制模版文件named.localhost 到文件uos.com.db2.修改uos.com.db文件 D.重启named服务E.配置DNS服务器F.测试DNS服务器 A.安装bind bind-utils yum -y install bind bind-utilso…

【02】Istio流量治理

2.1 Istio流量治理 Istio的流量路由规则使运维人员可以轻松控制服务之间的流量和API调用 Istio简化了诸如断路器,超时和重试之类的服务级别属性的配置,并使其易于设置重要任务(如A/B测试,canary部署和基于百分比的流量拆分的分段部…

ChatGPT-3.5 插件推荐:语音输入,视频总结,联网检索

前言 GPT4 里是有内置的插件市场的,不过博主一直觉得自己对这个工具的使用还不够到位,现在购买升级版性价比不划算所以暂时还没有开。不过今天在学习使用的时候,发现 GPT3.5 也是可以通过网页插件方式进行升级扩展的,而且功能还比…

【APUE】高级I/O

目录 一、五大 IO 模型 1.1 完整的 IO 过程 1.2 阻塞 IO 1.3 非阻塞 IO 1.4 信号驱动式 IO 1.5 多路转接 1.6 异步 IO 二、有限状态机编程 2.1 基本思想 2.2 数据中继模型 2.3 数据中继实现 2.4 中继引擎实现 三、IO多路转接 3.1 select 3.2 poll 3.3 epoll …

python3+requests+unittest实战系列【一】

1.环境准备 python3 pycharm编辑器 2.框架目录展示 (该套代码只是简单入门,有兴趣的可以不断后期完善) (1)run.py主运行文件,运行之后可以生成相应的测试报告,并以邮件形式发送;…

postswigger 靶场(CSRF)攻略-- 1.没有防御措施的 CSRF 漏洞

靶场地址: What is CSRF (Cross-site request forgery)? Tutorial & Examples | Web Security Academy (portswigger.net)https://portswigger.net/web-security/csrf 没有防御措施的 CSRF 漏洞 题目中已告知易受攻击的是电子邮件的更改功能,而目…

Python实现WOA智能鲸鱼优化算法优化BP神经网络回归模型(BP神经网络回归算法)项目实战

说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 鲸鱼优化算法 (whale optimization algorithm,WOA)是 2016 年由澳大利亚格里菲斯大学的Mirjalili 等提…

DeepFool: a simple and accurate method to fool deep neural networks

DeepFool: a simple and accurate method to fool deep neural networks----《DeepFool:一种简单而准确的欺骗深度神经网络的方法》 摘要 最先进的深度神经网络已经在许多图像分类任务上取得了令人印象深刻的结果。然而,这些相同的架构已被证明对于图像…

纯c语言模拟栈(初学必看)

1.栈的概念及其结构 栈是一种特殊的线性表,在栈这个结构里,越先存进去的数据越难取出来。 这个结构就像是一个只有一端有打开的容器,越先放进去的球越在底部,想要把底部的球拿出来,就必须先把前面的求拿出来。像这种”…

使用 ESP-IDF-SBOM 生成软件物料清单

概述 “软件物料清单” (SBOM) 已经成为软件安全和软件供应链风险管理的关键组成部分。SBOM 是与应用程序相关的所有软件组件、依赖项和元数据的详尽清单。 乐鑫认为,SBOM 信息是确保联网设备安全性的关键。因此,我们现在提供了相关工具和解决方案&…