数据结构(邓俊辉)学习笔记】词典 01—— 散列

文章目录

  • 1. 从服务到电话
  • 2. 循值访问
  • 3. 数组
  • 4. 原理
  • 5. 散列
  • 6. 冲突

1. 从服务到电话

在这里插入图片描述
现在进入新的一章词典。将学习实现词典 adt 的重要技术,也就是散列。我们将看到散列实际上并不是一种简单的技术,从某种意义上讲,它甚至是一种思想,是的,散列是一种赖以高效组织数据并实现相关算法的重要思想。

接下来我们就会看到,在这种思想背后的原理是多么的直观和简单。
在这里插入图片描述
我们的故事要从服务电话说起。当你需要获得某个公司或者部门的服务,你应该如何通过电话来找到他们呢?查电话簿,没错,这是一种司空见惯的方法。不过如果你手头没有电话簿,或者电话簿太厚,以至于你没有足够的耐心去查阅,又当如何呢?

作为一个公司,应该明白有相当多的客户都是因为这种纠结而流失掉了。因此反过来一家优秀的公司应该为它的服务取一个更加便于记忆的电话号码。

对此,你有什么建议?

你或许会说,那不妨就取 8个8或者8个6或者12345678,这些解决方案,尽管也勉强可行,但是如果从突出公司和服务个性的角度来看,这一类方法都是不够的。

下面不妨来看更有经验的公司是如何巧妙地解决这个问题的。

比如你如果需要致电 IBM 公司,就它的服务或产品进行咨询,那么你就可以拨打这个电话1-800-IBM-4YOU。没错,就是这个电话或者这个电话1-888-SHOP-IBM。没错,就是这个和这个电话。

如果你是第一次在这些公司的首页上看到这类电话,你或许会有所质疑,因为它并不像你直观理解的那样给出了一个完全由数字组成的电话号码。但是我相信很快就会习惯并且喜欢这种方式,并且由此对这类公司留下深刻而友好的印象。

实际上,你只要掏出你的电话,留意一下键盘。或许在此之前,对于每一个键,你只留意了其中的阿拉伯数字,而实际上当下大部分的键盘都是这种形式(其中每个键上除了有一个阿拉伯数字,同时还拥有若干个英文字母)。

我们可以理解为这样的一个键既可能对应于一个数字,也可能同时对应于若干个字母,也就是说由数字和字母混搭构成的电话号码也是可行的。

请留意体会这种方式的巧妙之处。

2. 循值访问

在这里插入图片描述
这种巧妙体现两个方面。

  1. 其一 通过这样一种形象生动而友好的方式,不仅可以使得你便捷地记住这个电话号码,同时更重要的是你能记住它是这家公司的电话号码。
  2. 另一方面,只要你能够辨识26个英文字母,你就可以通过这种方式来进行拨号,而你的每一次拨打依然对应于一串数字。整个的电话服务系统除了需要引入一种扩充之后的键盘,无需做任何更多的改造。

比如当你按照这样的提示(1-855-LENOVO)去拨打时,你所拨打的电话号码实际上依然是由数字组成的(1-855-253-6686)。实际上在这样一种巧妙的变换技巧背后,是某种深刻的思想。

我不妨就访问数据的方式来做一对比。

你应该记得对于不同的数据结构,我们在此之前都根据其如何访问数据进行过分类。你应该还记得有循秩访问 call by rank,比如向量,再如寻位置访问 call by position, 这方面的典型例子是列表 list,而以 BST 为典型代表的这类数据结构都属于循关键码访问,也就是 call by key。

反观,我们这里对电话号码的访问,如果说我们这里访问的对象是公司的服务,那么刚才这种获取服务的方式又属于其中的哪种呢?首先这种方式既不是循秩,也不是循位置访问,这是因为所有的公司的各项服务之间并不存在某种线性次序。

那么它是循关键码访问吗?这里的确有关键码,也就是每一个服务所对应的那个电话号码。然而即便你是按照刚才的方式去拨打某个特定的电话,在整个过程中,在你的脑海里除了那个公司和服务的助记符号之外,完全可以不出现任何形式的数字电话号码,因此这也不属于询关键码访问的方式。

那么我们刚才实质访问的是什么呢?是的,也就是你所需要找到的那个对象本身,我们称之为 value 数值。因此我们不妨将新的这种访问方式就称作循数值访问,call by value。

我们刚才已经领略到了这种新的访问方式的威力。是的,若能加以充分利用这种访问方式,将使我们的计算效率进一步得以提高,而这样的一种典型的技巧就是所谓的 hashing。中文可以根据含义译作杂凑,也可以根据发音,译作哈希,在这里不妨采用一种折中的译法,也就是散列。那么具体什么是散列呢?

3. 数组

在这里插入图片描述
看这样的一个具体实例,假设我们现在要为某一所高校制作一本电话簿。也就是说对于这个学校的每一位老师、学生以及员工或者办公室都可以通过这个电话簿直接找到对应的电话号码。

现在我们在进一步的假设,还有反过来的需求,也就是像现在的任何一部智能手机那样,对于任何一个有记录的电话号码都可以及时的给出机主的信息,那么这样一种需求应当如何来满足呢?

我想略作思考之后,你很可能就会认为这个问题的难度其实并不高。是的,表面看来确实如此,甚至不需要10行代码就能完成这个任务。比如你首先想到的很有可能就是数组,这种方法的原理可以表示为这样一幅图(上右图),这里由上而下就是一个线性数组,而在此区间之内的每一个秩,都对应于某一个电话号码,而每一个元素就可以用来记录对应机主的信息,比如一种方法就是我们通过电话号码确定对应的秩,找到对应的单元,并根据对应单元所给的引用间接地找到对应的机主。

这个是这样,其他的以及每一个单元都是这样。你甚至会说,嗯,这种方法不仅简明,而且高效,因为正如我们所看到的,从任何一个电话号码找到对应的机主记录,只需常数的时间,难道还有比这更高的效率吗?

是的,就实际效率而言,的确如此,但是不要忘了同时还有另一个因素需要兼顾。对,空间。

我们不妨来算一个账,就以老师所在的清华大学为例,不妨只考察座机。于是从理论上来说,清华大学所在的北京市的任何一部座机都有可能属于清华。因此如果采用上述的数组方式,数组的规模将高达10的8次方,也就是100M。那么清华大学实际拥有的固定电话又有多少门呢?据十多年前所获得的不完全统计,大致是在2至3万门之间,不妨粗略的故作为25K,于是我们就可以大致的估算出空间效率,具体来说也就是我们所用的空间总量与其中真正有效的数据项之比。

所谓不比不知道,一比吓一跳,可以看到空间效率只有万分之几,如此低下的效率是我们万万不能接受的,这样的实例还很多。这类应用的公共特点是我们需要存储和组织的数据项可能来自于一个相当大的空间,比如对于一个城市而言,理论上讲的100M门固定电话。

而在任何一个常规的时刻,我们所真正需要存储和组织的数据只是其中非常小的一个子集,因此,即便我们能够开出如此之大的数组,其空间效率也注定是极低的。那么如何破解这一难题呢?

4. 原理

在这里插入图片描述
实际上,我们刚才介绍的数组方法并非一无是处,就原理而言,只需再向前略作改进,也就成为了散列。

为此需要首先统一一些名词,比如在这里数组中的每一个单元将被称作为桶 bucket,其中可能直接存放某一个词条,也可能存放的是一个词条的间接引用。

而以上所说的数组在这里将被称为是桶数组(bucket array),或者直接的称之为散列表(hash table)。 我也统一地将散列表的长度记作 M。而这里的核心技巧恰恰就在于散列表长度的选取

首先,散列表至少应该能够足以容纳所有的待存放元素,因此这个不等号也就必须满足,其次正如我们已经看到的,直接应用数组的方法,它的致命缺点在于所使用的空间远远过大,因此所谓的散列表也可以认为是对这个空间做了一个适当的压缩。

既然是散列表的长度关乎我们的空间效率,所以这种压缩必须是实质性的。也就说在数量级上 M 要实质的圆小于 R,尽管 M 必须大于 N,但是为了实现尽可能高的效率,M 还是应该与 N 尽可能的同阶。

这样我们实际所花费的空间量就能够控制在线性的范围以内,那么这种思路如何兑现呢?应该记得在此前那种朴素的数组方法中,对于任何一个关键码,我们都可以在 O(1) 的时间内直接得到对应的记录或者它的间接引用。

而在做过空间压缩之后的现在,这个功能又当如何实现呢?如果将这种确定目标词条位置的功能,称作为定址,那么具体的定址方法也就是我们所说的散列hashing。为了完成散列定制,我们需要在事先确定一个散列函数,借助这个函数可以将任意关键码转换为对应的词条或它的入口。

在这个原理图中,原先一蹴而就的过程被分解为了两步,也就是当任意给定的关键码通过 hash function 转换为散列表中的某一个桶单元并且进而通过这个桶单元找到目标词条。

尽管整个过程多了一些曲折,但是正如我们最终将要看到的,只要散列函数设计已实现得当,我们就可以将整个过程所需要的时间控制在期望的常数范围以内。

那么这里的散列表应当如何压缩并得到呢?相应的散列函数又当如何设计呢?

5. 散列

在这里插入图片描述
再次回到清华大学电话簿的那个实例,假设这就是所使用的散列表,可以看到散列表的长度在这里取做90,001,这一取值的确符合我们的设计原理,也就是一方面要在数量级上远远的小于10的8次方,同时相对于我们实际需要存放的记录数,也就是大致25K, 表长既能够足够大,同时又在数量级上保持同阶。

经过这样的压缩,空间利用率将上升至少为1/4。再来看散列函数,对于任何一个关键码,该散列函数都将它映射为它相对于表长 M 的整数模余。也就说任何一个8位的电话号码都将经过相对于90,001的整除被映射至相应的余数。

我们看几个具体的实例,不妨先掏出计算器,已备待会的验算之需,首先来看中间这个电话号码,根据它在经过对90001的整数取模就可以得到反列表中的一个桶,而这个桶则正确地指向了机主所对应的词条。下面这个电话号码也是如此,根据它经过对90001的取模,同样可以找到桶单元,而根据这个桶单元也同样可以找到机主所对应的词条记录。事实上,只要这个电话号码的确属于清华大学,按照这样的一个算法,的确都可以获得相应的机主信息。

至此,我们已经基本上圆满地实现了这个应用需求,难道不是吗?从每一个电话号码到对应的机主信息,只需要常数的时间。而空间效率呢?按照刚才的估算,也足以令人满意。特别的,对于散列表而言,其空间效率主要取决于 N 与 M 的比值,这一比值也称作装填因子(load factor)。 通常也简记作 λ \lambda λ。是的,就目前而言,我们的解决方法的确几乎完美。然而,这里的故事依然没有结束。

6. 冲突

在这里插入图片描述
再次回到刚才电话号码簿的实例,散列表的长度依然,散列函数也保持不变。于是刚才的三个电话号码也将保持同样的映射。

现在假设还同时需要存放第四个电话号码。如果你的计算器在手边,不妨将它关于90001做一个整除取模的运算,你会发现什么呢?是的,这个电话号码居然也会被映射到51304这个位置。

这个情况也称作散列冲突 hash collision。也就是说两个不同的关键码有可能会被映射到同一个桶单元。一山要容两虎,这是很难做到的。当然,适当的将装贴因子降低或者等效地说将散列表取得更长,的确能够在一定程度上降低这种情况发生的概率,但是很遗憾,这类冲突几乎是不能杜绝的。

对散列函数稍加观察,就不难发现这背后的原因。因为所谓的散列函数可以理解为是将来自于更大的一个定义域R中的元素映射到相对而言远远更小的一个取值域。形象地说,我们有很多很多的鸽子,但是只有寥寥无几的笼子。根据鸽巢原理,这种冲突必然是无法彻底避免。

当然,好消息就是,只要策略和方法得当,我们完全可以将此类冲突的概率控制在一个足够低的范围。为此我们既要研究散列函数的设计方法,更要研究在冲突依然发生时,如何有效的排解。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

记录一次环境的安装

目录 新添加的代码 代码解释 为啥ubuntu用debian软件源 为啥修改sources.list.d S权限意思 php缺少和数据库连接的模块 使用root登陆数据库1698错误 字段解释 auth_socket解释 使用root登陆数据库方法 详细解释 首先在安装的时候,有一个dockerfile文件&a…

day 18流的定位、文件IO以及Linux系统中时间的获取

流的定位 偏移量:读和写都在偏移量的位置进行 文件IO 相对于标准IO来说,文件IO直接在Linux的内核中操作,也更加的简洁精炼 对文件的操作也是三个部分 1.打开文件 open 2.读写文件 read write 3.关闭文件 close 还有一些其他的函数接口…

vue3 命令运行窗口暴露网络地址,以及修改端口号

一般情况下这里的地址是隐藏的 这里加上 --host 可以暴露网络地址,再加上--port --8080 就可以将端口号修改为8080(修改后边的数字就可以修改为你想要的端口号)

linux安装配置jdk

①下载jdk安装包,放在/opt/app/software/java下 cd /opt/app/software/java②进行解压操作 tar -zxvf jdk-8u251-linux-x64.tar.gz③解压完成之后,进行环境变量的配置,shell下执行 vi ~/.bash_profile根据jdk的安装目录,加入 …

【C++】学习笔记——智能指针

文章目录 二十一、智能指针1. 内存泄漏2. 智能指针的使用及原理RAII智能指针的原理auto_ptrunique_ptrshared_ptrshared_ptr的循环引用weak_ptr删除器 未完待续 二十一、智能指针 1. 内存泄漏 在上一章的异常中,我们了解到如果出现了异常,会中断执行流…

LocalDateTime计算两个时间之间的间隔

LocalDateTime计算两个时间之间的间隔 嘚吧嘚LocalDateTimeLocalDateLocalTime 嘚吧嘚 自从认识了LocalDateTime之后,使用的频率越来越高了,使用多了就不可避免的涉及到日期的比较、加减以及计算日期间隔这些操作。 但是我发现自己好像不会&#x1f605…

2024年钉钉杯大学生大数据挑战赛倒计时,最后冲刺

2024第三届钉钉杯大学生大数据挑战赛倒计时,小编给大家带来非常实用的最后冲刺助力【A题】,(看图资料预览): 中国烟草行业作为国家税收和财政收入的重要支柱,近年来销售收入持续增长。国家对此实行严格的专…

一键测量仪,能否彻底解决燃气灶配件缺陷问题?

燃气灶配件是指用于燃气灶的附件或零部件,用于安装、维护或改进燃气灶的功能和性能。这些配件通常包括各种零部件、附件和替换件,以确保燃气灶的正常运行和安全使用。燃气灶的火焰头是产生火焰的部件,通常根据不同的燃气类型和火力需求选择合…

ETL数据集成丨快速将MySQL数据迁移至Doris数据库

随着大数据技术的迅速发展,越来越多的企业开始寻求高效、灵活的数据存储与分析解决方案。Apache Doris(原名 Palo)作为一款高性能的MPP(大规模并行处理)分析型数据库,凭借其在OLAP场景下的卓越表现&#xf…

Minio多主机分布式 docker-compose 集群部署

参考 docker-compose搭建多主机分布式minio - 会bk的鱼 - 博客园 (cnblogs.com) 【运维】docker-compose安装minio集群-CSDN博客 Minio 是个基于 Golang 编写的开源对象存储套件,虽然轻量,却拥有着不错的性能 中文地址:MinIO | 用于AI的S3 …

SYD88xx代码复位不成功和解决办法

原来的复位代码如下: void ota_manage(void){#ifdef _OTA_if(ota_state){switch(ota_state){case 1 : #if defined(_DEBUG_) || defined(_SYD_RTT_DEBUG_)dbg_printf("start FwErase\r\n");#endifCmdFwErase();#if defined(_DEBUG_) || defined(_SYD_RTT_DEBUG_)db…

Spring Boot 动态数据源

目录 前言 前置环境 pom yml Entity Dao 枚举类 数据源 AOP Controller 启动类 演示 前言 大多数系统中,都需要数据库来持久化数据,在大多数情况下,一个系统只需要配置一个数据源便能够完成所有业务的查询,保存操作。…

为什么Transformer需要进行 Multi-head Attention?

目录 1. 前言 2. 基本概念 2.1. Word2Vec 2.2. Attention is all you need 2.3. Self-attention 2.3.1. 概述self-attention 2.3.2. 训练细节 2.4. Multi-head Attention 2.4.1. 多头理论细节 2.4.2. 多头代码实现 2.5. 总结 3. 讨论观点 3.1. 观点1: …

【工具插件类教学】vHierarchy 2工具编辑器扩展使用

目录 一、下载导入 二、使用介绍 1.便捷小工具 a.图标和颜色Icons and colors b.对象组件缩略图Component minimap c.层级线展示Hierarchy lines d.极简模式Minimal mode e.斑马条纹图案Zebra striping f.激活切换Activation toggle 2、快捷键 一、下载导入 资源官方…

二维码门楼牌管理应用平台建设:流程优化与全面考量

文章目录 前言一、工作流程优化:移动端采集与实时更新二、数据完整性与准确性保障三、效率提升与成本节约四、扩展性与未来发展五、数据安全与隐私保护六、用户培训与技术支持 前言 随着智慧城市建设的不断深入,二维码门楼牌管理应用平台作为城市管理的…

数据库事务处理技术——故障恢复

1. 数据故障恢复的宏观思路 我们知道DBMS是利用内存(主存)和外存(辅存)这样的存储体系进行数据库的管理,其中内存也就是我们常说的缓存是易失的。而事务时DBMS对数据库进行控制的基本单元,宏观上是由程序设…

算法训练.

一.奶牛晒衣服 题解: 这应该是个二分题,但是我用的是贪心暴力写的,思想就是循坏每次都让湿度最高的使用一次烘衣机,要是湿度最高的可以在自然条件下都能晒干就结束循环,这样内部我第一想法就是每次都排个降序&#xf…

windows下在线预览服务kkFileView4.4.0问题记录

前几天找到一个开源项目:kkFileView,感觉可能以后可能会用到,所以尝试了下。 通过git下载下来,版本是4.4.0,通过idea打开项目,发现老是无法找到组件aspose-cad,版本是23.9. 找了好多文章&#x…

AI学习(1)软件的选择,cuda和pytorch的安装

文章目录 1.使用VScode开发,结合anaconda配置python环境2.安装pytorch库3.深度学习相关的库1.numpy(科学计算库)2.pandas(数据分析处理库)3.matplotlib(可视化库)4.seaborn(可视化库) 1.使用VSc…

Docker三大基础组件

Docker有三个重要的概念:仓库、镜像和容器 ,它们是Docker的三大基础组件,这三个组件共同构成了Docker的核心架构,使得Docker能够实现对应用程序的便捷打包、分发和运行。 Docker使用客户端-服务器体系结构。Docker客户端与Docker守…