【追求卓越09】算法--散列表(哈希表)

引导

        通过前面几个章节的学习(二分查找,跳表),我们发现想要快速查找某一个元素,首先需要将所有元素进行排序,再利用二分法思想进行查找,复杂度是O(logn)。有没有更快的查找方式呢?

        本章介绍一下我们工作中经常接触到的散列表(哈希表)。它能够使查找的效率达到O(1)。主要是理论方面,让大家开始了解哈希思想。

散列表

        提到查找复杂度是O(1),我们在前面接触到的就是数组了。散列思想就是基于数组支持下标随机访问数据特想实现的。那么问题就是如何将元素和数组下标进行映射?

        例:学校开运动会,有100个运动员,编号是0~99。我要查找86号运动员的信息,该怎么做呢?

        思路:我们可以建立一个数组,将标号和数组下标一一对应,访问哪一个标号的运动员,直接查找对应数组下标即可

        该散列思想中,编号我们称作为键或者关键字。将关键字转换为数组下标的,我们称作为散列函数。而散列函数计算得到的值就叫做散列值(数组下标)。

        散列思想:散列表用的就是数组支持按照下标随机访问。当我们通过散列函数将元素的键值映射为数组下标时,然后将数组保存到数组对应位置中。当我们查找都一个元素时,我们使用同一个散列函数,将键值转化为数组下标,从对应的数组下标获取数据。

散列函数

        从散列思想中,我们可以知道散列表中散列函数起到很重要的作用。针对不同的问题,散列函数都可能不同。比如上面的例子,我可以设置这样的哈希函数:

int hash(int key)
{
    return key;
}
/*
将关键字作为下标*/

由于该例子较为简单,也比较容易想到。但是无论什么想的问题,我觉得散列函数应该尽量做到一下几点:

  1. 散列函数计算得到的散列值是一个非负整数。因为散列值会作为数组下标。
  2. 如果key1 == key2,那么hash(key1)==hash(key2)。
  3. 如果key1 != key2,那么hash(key1)!=hash(key2)。

其中1,2好理解,但是3实际上是很实现的。因为数组的大小是有限的,但是不同元素可能会有很多。这就导致肯定会存在key1 != key2,hash(key1)==hash(key2)的情况。这种情况我们称之为散列冲突。再优秀的散列函数也避免不了散列冲突

散列冲突的解决方式

散列冲突的解决方式有两种:开放寻址法,链表法。

开放寻址法

开放寻址法的思想是:出现了散列冲突,我们就重新探测到一个空闲位置,将其插入

如何探测空闲位置?

        如果某个数据经过散列函数之后,存储位置已经被占用了,我们就从当前位子开始,依次往后查找,看是否有空闲位子,直到找到为止。

如何查找?

        通过散列函数求出要查找出的键值对应的散列值,然后比较数组中下标为散列值的元素和要查找的元素。如果相等,则说明是我们要找的元素;否则继续往后查找。如果遍历到空闲位子,还没有找到,则说明散列表中没有要查找的元素。

删除的注意事项?

        我们知道散列表有查找操作肯定也有删除操作。当我们删除一个元素时,如果将其设置为空闲,那么在查找的过程中就会出现问题(原本在散列表中的元素,会认定为不存在)。我们可以将删除的元素置为delete状态。该状态的位子,可以插入数据。查找的时候,不必停下来,继续向后查找。

开放寻址法的缺点?

        从开放寻址法的思想中,我们可以考虑到,当散列表中的元素越来越多的时候,散列冲突可能性越来越大,空闲位置越来越少,线性探测的时间就会越来越久。极端情况下,我们可能需要探测整个散列表,最坏的情况下,时间复杂度是O(n)。

装载因子

一般情况下,我们会尽量保证散列表中有一定比例的空闲槽位,我们用装载因子表示空位的多少。

散列表的装载因子=填入表中的元素个数/散列表的长度

链表法

        链表法相对于开发寻址法较为简单,也容易理解。使用的是指针数组结构。数组元素对应的是一个链表,所有散列值相同的元素都在一个链表中。如图:

        那么链表法的插入操作,我们选择链表的头插,所以时间复杂度是O(1)。

        但是查找和删除操作的时间复杂度是O(k),k表示链表的长度。理论上每个链表的长度应该是均匀的,k=n/m。m是散列表的长度。这就要求散列表的优秀。若散列表不够好,那么查找和删除的复杂度可能会退化到O(n)。

如何设计散列函数

        我们知道散列函数是散列表中重要的一个部分,它的好坏,决定了散列冲突的概率以及散列表的性能。

        散列函数在设计的时候除了尽量遵循,上章节中指明的三点,还要考虑下面几点因素:

  1. 散列函数不能设计的太复杂。复杂的散列函数会消耗过多的资源,间接影响性能。
  2. 散列值尽量随机并且分布均匀。这样才能降低散列冲突的概率,即使出现散列冲突,每个链表长度也比较均匀(针对链表法)

        常见的散列函数的设计方法:数据分析法,直接寻址法,平方取中法,折叠法,随机数法等。

装载因子调整

        我们知道当装载因子过大的时候,说明散列表中的元素越多,空闲位置越少,散列冲突的概率就会越大。

        对于没有频繁插入和删除的静态散列表,我们可以设计出一个完美的散列表。因为数据都是我们提前已知的。

        但是对于动态的散列表,数据集合是动态变化的。我们无法事先申请一个足够大的散列表。但是当装载因子逐渐变大,散列冲突变得无法接受的时候,我们就需要动态扩容。

        比如当散列表的装载因子是0.8时,我们将散列表扩容一倍,那么装载因子就变为了0.4。仅仅将散列表扩容就可以吗?

        散列表扩容之后,我们需要将原先的散列表拷贝到新的散列表中。这个过程需要重新进行散列函数计算。

问题:我们知道在扩容的时候,涉及到数据的搬移,这会消耗很多时间。这就导致响应不及时。该如何处理?

解:这种动态扩容,直接将数据进行搬移的方式,非常的耗时,用户体验很不好(出现概率较低)。我们可以进行优化,将数据搬移的操作进行分批处理。第一次进行扩容,我们只申请散列表,并将数据插入,不进行数据的搬移。当下次再插入数据时,我们从旧的散列表中拷贝一个到新的散列表(旧的delete)。一直当旧的散列表全部被搬移完。在这个过程中,我们查询时,需要先旧的散列表中查询,查询不到,再到新的散列表中查询。如果都没有,说明不存在。这种均摊的方式,将任何情况下,插入操作的时间复杂度是O(1);

选择散列冲突的解决方案

        完全通过动态扩容来解决散列冲突是不可能的。从上一节中,我们知道解决散列冲突的方法有两种,开放地址法和链表法。这两者都项目中都有使用,但是场景不同。

开放地址法

开放地址法,我们知道是将数据都放到数组中,这就能够利用操作系统的局部性原理(CPU缓存),提高访问效率。这是它的优点

缺点:

  1. 删除较为麻烦,需要进行特殊标记
  2. 冲突概率较高
  3. 因为装载因子不能大于1,故相比较于链表法,更加浪费内存空间

总结:开放地址法适用于数据量较小,装载因子较小的情景

链表法

链表法的优点:

  1. 内存的利用率较高,可以使用时再申请
  2. 能够容忍大装载因子。只要元素均分,即使装载因子为10,也只是链表长度变长了。即使链表长度很长,我们也可以通过跳表,红黑树等方式,增加查询效率。

缺点:

  1. 储存在内存中不连续,对CPU不友好
  2. 需要存储指针,浪费内存。(对于大的数据对象,也可以忽略)

总结:链表法适用于数据量大,装载因子较大的场景。适合储存对象比较大

总结

        本章我们主要介绍一些散列表相关的概念:散列函数,散列冲突,散列值,关键值,以及散列冲突的解决方式。

        散列函数设计的好坏决定了散列冲突的概率,也决定了散列表的性能。想要设计一个好的散列表,需要从散列函数,装载因子,散列冲突解决方案着手。

        关于散列函数,我们不能设计的太复杂,并且尽量使散列值均匀分布,这样尽可能减少散列冲突,即便散列冲突,链表长度也较为均匀。

        关于装载因子,我们需要设置一个合理的装载因子上限,并在动态扩容的过程中,需要考虑均分搬移数据

        关于散列冲突解决方案,有开放地址法和链表法。两者都有适用的情景,那么我们就要针对情况进行选择。不过链表法总体而言较为通用

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

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

相关文章

2023年【上海市安全员C证】考试及上海市安全员C证找解析

题库来源:安全生产模拟考试一点通公众号小程序 2023年上海市安全员C证考试为正在备考上海市安全员C证操作证的学员准备的理论考试专题,每个月更新的上海市安全员C证找解析祝您顺利通过上海市安全员C证考试。 1、【多选题】2017年9月颁发的《中共上海市委…

el-tree 与table表格联动

html部分 <div class"org-left"><el-input v-model"filterText" placeholder"" size"default" /><el-tree ref"treeRef" class"filter-tree" :data"treeData" :props"defaultProp…

基于区域划分的GaN HEMT 准物理大信号模型

GaN HEMT器件的大信号等效电路模型分为经验基模型和物理基模型。经验基模型具有较高精度但参数提取困难&#xff0c;特别在GaN HEMT器件工艺不稳定的情况下不易应用。相比之下&#xff0c;物理基模型从器件工作机理出发&#xff0c;参数提取相对方便&#xff0c;且更容易更新和…

【DevOps】Git 图文详解(五):远程仓库

Git 图文详解&#xff08;五&#xff09;&#xff1a;远程仓库 1.远程用户登录1.1 &#x1f511; 远程用户登录&#xff1a;HTTS1.2 &#x1f511; 远程用户登录&#xff1a;SSH 2.远程仓库指令 &#x1f525;3.推送 push / 拉取 pull4.fetch 与 pull 有什么不同 &#xff1f; …

【C++】vector的介绍与使用

&#x1f9d1;‍&#x1f393;个人主页&#xff1a;简 料 &#x1f3c6;所属专栏&#xff1a;C &#x1f3c6;个人社区&#xff1a;越努力越幸运社区 &#x1f3c6;简 介&#xff1a;简料简料&#xff0c;简单有料~在校大学生一枚&#xff0c;专注C/C/GO的干货分…

数据库实验二 数据库表的数据插入、修改、删除操作

数据库实验二 数据库表的数据插入、修改、删除操作 一、实验目的二、设计性实验三、观察与思考 一、实验目的 1&#xff0e;掌握MySQL数据库表的数据插入、修改、删除操作SQL语法格式 2&#xff0e;掌握数据表的数据的录入、增加和删除的方法 二、设计性实验 某超市的食品管…

报错注入 [极客大挑战 2019]HardSQL1

打开题目 输入1或者1"&#xff0c;页面均回显NO,Wrong username password&#xff01;&#xff01;&#xff01; 那我们输入1 试试万能密码 1 or 11 # 输入1 and 12 # 输入1 union select 1,2,3 # 输入1 ununionion seselectlect 1,2,3 # 输入1 # 输入1# 页面依旧回…

redis运维(十六) 有序集合

一 有序集合 把握一点&#xff1a; 各种redis 命令都提供各种语言对应的API 接口,后续API是关键 ① 概念 1、sorted set --> 有序集合2、redis有序集合也是集合类型的一部分&#xff0c;所以它保留了集合中元素不能重复的特性3、但是不同的是,有序集合给每个元素多设置…

《Fine-Grained Image Analysis with Deep Learning: A Survey》阅读笔记

论文标题 《Fine-Grained Image Analysis with Deep Learning: A Survey》 作者 魏秀参&#xff0c;南京理工大学 初读 摘要 与上篇综述相同&#xff1a; 细粒度图像分析&#xff08;FGIA&#xff09;的任务是分析从属类别的视觉对象。 细粒度性质引起的类间小变化和类内…

MAX/MSP SDK学习05:A_GIMME方法

今天终于将A_GIMME方法部分的描述看懂了&#xff0c;上周因为太赶时间加上这文档很抽象一直没看懂。也就那么一回事&#xff0c;记录一下。 A_GIMME方法用于接收多个参数。 ①内置消息选择器传递多个参数时一定要使用A_GIMME&#xff1b; ②自定义消息选择器传递多个参数时建…

git常用命令总结

一&#xff0c;撤销本地commit的记录&#xff0c;注意是还没有push到远端的奥 androidstudio上操作&#xff1a;选中要提交的记录的上一个记录&#xff0c;也就是你想回退到的某个记录。如你提交的第55个commit有问题&#xff0c;想撤回&#xff0c;你就选中第54个记录&#x…

每日一题:LeetCode-589.N叉树的前序遍历

每日一题系列&#xff08;day 01&#xff09; 前言&#xff1a; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f50e…

【鸿蒙应用ArkTS开发系列】- 灌水区,鸿蒙ArkTs开发有问题可以在该帖中反馈

大家好, 这是一篇水贴&#xff0c;给大家提供一个交流沟通鸿蒙开发遇到问题的地方。 新增新增这个文章呢&#xff0c;大家在开发使用ArkTS开发鸿蒙应用或者鸿蒙服务的时候&#xff0c;有遇到疑问或者问题&#xff0c;可以在本文章评论区提问&#xff0c;我看到了如果知道怎么…

易货:一种新型的商业模式

随着经济的发展和社会的进步&#xff0c;人们对于交易的需求和方式也在不断变化。传统的商业模式已经无法满足人们对于多元化、个性化、高效的需求。在这样的背景下&#xff0c;易货模式逐渐走进人们的视野&#xff0c;成为一种新型的商业模式。 易货模式是一种以物换物的交易方…

优秀智慧园区案例 - 三亚市崖州湾科技城智慧园区,先进智慧园区建设方案经验

一、项目背景 三亚崖州湾科技城作为海南自贸港建设的重点园区&#xff0c;是重点推进的海南自贸港先导项目之一。崖州湾科技城全力抢抓有利时机&#xff0c;进一步拓宽发展思路&#xff0c;持续深化体制机制创新&#xff0c;牢牢把握“打造产学研城深度融合的聚集地”这一核心…

【Hello Go】Go语言文本文件处理

文本文件处理 字符串处理字符串操作ContainsJoinindexrepeatReplaceSplitTrimFields 字符串转换AppendFormatParse 正则表达式Json处理编码Json通过结构体生产Json通过map生产json 解码Json解析到结构体解析到interface 文件操作相关api介绍建立和打开文件关闭文件写文件读文件…

kaggle新赛:SenNet 3D肾脏分割大赛(3D语义分割)

赛题名称&#xff1a;SenNet HOA - Hacking the Human Vasculature in 3D 赛题链接&#xff1a;https://www.kaggle.com/competitions/blood-vessel-segmentation 赛题背景 目前&#xff0c;人类专家标注员需要手动追踪血管结构&#xff0c;这是一个缓慢的过程。即使有专家…

Docker安装Zookeeper

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

pytorch下载离线包的网址

下载地址&#xff1a;https://download.pytorch.org/whl/torch_stable.html 安装GPU版本需要安装&#xff1a;torch、torchvision、 注意版本需要对应上 格式&#xff1a;适用cuda版本&#xff0c;torch版本 或者 orchvision版本&#xff0c;cp38就是适用python 3.8版本 下…