数据结构-06-散列/哈希表

1-什么是散列表

       散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表。散列表中的元素在数组的位置(index)是通过散列函数得到的。
     

2-散列函数

      散列函数在散列表中起着非常关键的作用。散列函数,顾名思义,它是一个函数。我们可以把它定义成hash(key),其中key表示元素的键值,hash(key)的值表示经过散列函数计算得到的散列值。那么设计一个散列函数的基本要满足如下的要求:
      (1)散列函数计算得到的散列值是一个非负整数;因为数组的下标就是从0开始的。
      (2)如果key1 = key2,那hash(key1) == hash(key2);同一个key得到的散列函数的值应该是一样的。
      (3)如果key1 ≠ key2,那hash(key1) ≠ hash(key2);但是在真实的情况下,要想找到一个不同的key对应的散列值都不一样的散列函数,几乎是不可能的。即便像业界著名的MD5,SHA等哈希算法,也无法完全避免这种散列冲突。而且,因为数组的存储空间有限,也会加大散列冲突的概率。
 

2.1-如何设计散列函数

一个好的散列函数需要满足下面几个条件:

       首先,散列函数的设计不能太复杂。过于复杂的散列函数,势必会消耗很多计算时间,也就间接的影响到散列表的性能。
       其次,散列函数生成的值要尽可能随机并且均匀分布,这样才能避免或者最小化散列冲突,而且即便出现冲突,散列到每个槽里的数据也会比较平均,不会出现某个槽内数据特别多的情况。

       实际工作中,我们还需要综合考虑各种因素,实际场景等。这些因素有关键字的长度、特点、分布、还有散列表的大小等。

3-散列冲突

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

3.1-开放寻址法

       开放寻址法的核心思想是,如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入。那如何重新探测新的位置呢?一个比较简单的探测方法,线性探测(Linear Probing)。当我们往散列表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。

                  

      在元素X插入散列表之前,已经4个元素(蓝色是已经存在的)插入到散列表中。X经过hash算法之后,被散列到位置下标为3的位置,但是这个位置已经有数据了,所以就产生了冲突。所以顺序地往后一个一个找,直到找到空闲位置5,于是将元素X插入到下标等于5的位置。

       当散列表中插入的数据越来越多时,散列冲突发生的可能性就会越来越大,空闲位置会越来越少,线性探测的时间就会越来越久。极端情况下,我们可能需要探测整个散列表,所以最坏情况下的时间复杂度为O(n)。同理,在删除和查找时,也有可能会线性探测整张散列表,才能找到要查找或者删除的数据。

       开放寻址法解决冲突的散列表,删除数据的时候比较麻烦,需要特殊标记已经删除掉的数据。而且,在开放寻址法中,所有的数据都存储在一个数组中,比起链表法来说,冲突的代价更高。所以,使用开放寻址法解决冲突的散列表,装载因子的上限不能太大。这也导致这种方法比链表法更浪费内存空间。

       所以当数据量比较小、装载因子小的时候,适合采用开放寻址法。这也是Java中的ThreadLocalMap使用开放寻址法解决散列冲突的原因

        对于开放寻址冲突解决方法,除了线性探测方法之外,还有另外两种比较经典的探测方法,二次探测(Quadratic probing)和双重散列(Double hashing)

       所谓二次探测,跟线性探测很像,线性探测每次探测的步长是1,那它探测的下标序列就是hash(key)+0,hash(key)+1,hash(key)+2……而二次探测探测的步长就变成了原来的“二次方”,也就是说,它探测的下标序列就是hash(key)+0,hash(key)+1^2,hash(key)+2^2......

       所谓双重散列,意思就是不仅要使用一个散列函数。我们使用一组散列函数hash1(key),hash2(key),hash3(key)……我们先用第一个散列函数,如果计算得到的存储位置已经被占用,再用第二个散列函数,依次类推,直到找到空闲的存储位置。

3.2-链表法

       链表法是一种更加常用的散列冲突解决办法,相比开放寻址法,它要简单很多。所有散列值相同的元素我们都放到相同槽位对应的链表中。

      当插入的时候,我们只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的时间复杂度是O(1)。当查找、删除一个元素时,我们同样通过散列函数计算出对应的槽,然后遍历链表查找或者删除。

       链表法对内存的利用率比开放寻址法要高。因为链表结点可以在需要的时候再创建,并不需要像开放寻址法那样事先申请好。链表法比起开放寻址法,对大装载因子的容忍度更高。开放寻址法只能适用装载因子小于1的情况。接近1时,就可能会有大量的散列冲突,导致大量的探测、再散列等,性能会下降很多。但是对于链表法来说,只要散列函数的值随机均匀,即便装载因子变成10,也就是链表的长度变长了而已,虽然查找效率有所下降,但是比起顺序查找还是快很多。
       链表因为要存储指针,所以对于比较小的对象的存储,是比较消耗内存的,还有可能会让内存的消耗翻倍。而且,因为链表中的结点是零散分布在内存中的,不是连续的,所以对CPU缓存是不友好的,这方面对于执行效率也有一定的影响。如果我们存储的是大对象,也就是说要存储的对象的大小远远大于一个指针的大小(4个字节或者8个字节),那链表中指针的内存消耗在大对象面前就可以忽略了。

       基于链表的散列冲突处理方法比较适合存储大对象、大数据量的散列表,而且,比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表

4-装载因子

       装载因子越大,说明散列表中的元素越多,空闲位置越少,散列冲突的概率就越大。不仅插入数据的过程要多次寻址或者拉很长的链,查找的过程也会因此变得很慢。例如Java中HashMap的装载因子就是0.75。装载因子阈值需要选择得当。如果太大,会导致冲突过多;如果太小,会导致内存浪费严重。装载因子阈值的设置要权衡时间、空间复杂度。如果内存空间不紧张,对执行效率要求很高,可以降低负载因子的阈值。

       大部分情况下,动态扩容的散列表插入一个数据都很快,但是在特殊情况下,当装载因子已经到达阈值,需要先进行扩容,再插入数据。这个时候,插入数据就会变得很慢,甚至会无法接受。为了解决一次性扩容耗时过多的情况,我们可以将扩容操作穿插在插入操作的过程中,分批完成。当装载因子触达阈值之后,我们只申请新空间,但并不将老的数据搬移到新散列表中。

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

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

相关文章

【MyBatis-Plus】MyBatis进阶使用

目录 一、MyBatis-Plus简介 1.1 介绍 1.2 优点 1.3 结构 二、MyBatis-Plus基本使用 2.1 配置 2.2 代码生成 2.3 CRUD接口测试 三、MyBatis-Plus策略详解 3.1 主键生成策略 3.2 雪花ID生成器 3.3 字段自动填充策略 3.4 逻辑删除 四、MyBatis-Plus插件使用 4.1 乐…

从零开始:VuePress2 + GitHub Pages 搭建你的第一个免费博客网站

可能你也想拥有一个属于自己的博客网站,但是自己搭个博客网站不知道从何下手,而且还需要租个云服务器,虽然一个月只需几十块钱,但是我们的博客网站是要长期维护的,日积月累也要不少钱呢。 现在我就教你用 VuePress2 …

JVM-1-运行时数据区

程序计数器(Program Counter Register) 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里[1],字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令&…

【ICCV 2022】Masked Autoencoders Are Scalable Vision Learners

何凯明一作文章:https://arxiv.org/abs/2111.06377 本文的出发点:是BERT的掩码自编码机制:移除一部分数据并对移除的内容进行学习。mask自编码源于CV但盛于NLP,恺明对此提出了疑问:是什么导致了掩码自编码在视觉与语言…

Apache OfBiz 反序列化命令执行漏洞(CVE-2023-49070)

项目介绍 Apache OFBiz是一个非常著名的电子商务平台,是一个非常著名的开源项目,提供了创建基于最新J2EE/XML规范和技术标准,构建大中型企业级、跨平台、跨数据库、跨应用服务器的多层、分布式电子商务类WEB应用系统的框架。OFBiz最主要的特…

自定义注解

自定义注解 自定义注解 以实战案例为驱动,快速掌握此怎么自己自定义注解,也好出去自己吹牛逼~哈哈哈 假设我们打车,需要检验验证码,我们需要一个注解字来进行核验,我们怎么操作呢? 大纲总览 ​​ 1.定义注解 可以自己创一个包单门存放自己的注解: 如​constraints​ 包 然后…

Linux高级系统编程-MySQL数据库基本使用语法

DDL:数据定义语言 作用 : 用于完成对数据库对象(数据库、数据表、视图、索引等)的创建、删除、修改 数据库操作 查询数据库 - > 显示当前 mysql 中的数据库列表 > show databases; - > 显示指定名称的数据的创建的 SQL 指令 > show c…

车规MCU应用场景及国产替代进展

目录 1.车规MCU应用场景 1.1 车身域 1.2 动力底盘域 1.3 座舱域和智驾域 1.4 网联域 2.国产替代进展 3.小结 前面一篇文章征途漫漫:汽车MCU的国产替代往事-CSDN博客对车规MCU国产替代的背景与一些往事进行了简单叙述,今天来聊聊车规MCU具体会在汽车哪些地方用…

sql宽字节注入

magic_quotes_gpc(魔术引号开关) https://www.cnblogs.com/timelesszhuang/p/3726736.html magic_quotes_gpc函数在php中的作用是判断解析用户提交的数据,如包括有:post、get、cookie过来的数据增加转义字符“\”,以…

PDF如何转换制作成翻页电子书

很多朋友想将PDF转换制作成一本翻页电子书,却不知道如何操作。其实,转换翻页电子书的过程并不难,只需要掌握一些基本的技巧和方法就可以了。 基本该怎么操作呢? 1.首先需要一个工具帮助我们成功转换,推荐使用FLBOOK这…

【LeetCode刷题笔记】155.最小栈

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 更多算法知识专栏&#xff1a;算法分析&#x1f525; 给大家跳段街舞感谢…

滑动窗口最大值(LeetCode 239)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路方法一&#xff1a;暴力法方法二&#xff1a;优先队列方法三&#xff1a;单调队列 参考文献 1.问题描述 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动…

【LeetCode刷题-树】--173.二叉搜索树迭代器

173.二叉搜索树迭代器 本题就是实现二叉树的中序遍历&#xff0c;利用数组本身实现迭代器 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.va…

如何在Docker部署draw.io流程图软件并实现公网远程访问

前言 提到流程图&#xff0c;大家第一时间可能会想到Visio&#xff0c;不可否认&#xff0c;VIsio确实是功能强大&#xff0c;但是软件为收费&#xff0c;并且因为其功能强大&#xff0c;导致安装需要很多的系统内存&#xff0c;并且是不可跨平台使用。所以&#xff0c;今天给…

牛客小白月赛83 解题报告

题目链接&#xff1a; https://ac.nowcoder.com/acm/contest/72041#question A题 解题思路 签到 代码 #include <bits/stdc.h> using namespace std;int main() {int a, b, c, d, e;cin >> a >> b >> c >> d >> e;int A, B, C, D…

【人工智能】实验二: 洗衣机模糊推理系统实验与基础知识

实验二: 洗衣机模糊推理系统实验 实验目的 理解模糊逻辑推理的原理及特点&#xff0c;熟练应用模糊推理。 实验内容 设计洗衣机洗涤时间的模糊控制。 实验要求 已知人的操作经验为&#xff1a; “污泥越多&#xff0c;油脂越多&#xff0c;洗涤时间越长”&#xff1b;“…

DDD挤水分和强行加异性为好友-UMLChina建模知识竞赛第4赛季第25轮

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 参考潘加宇在《软件方法》和UMLChina公众号文章中发表的内容作答。在本文下留言回答。 只要最先答对前3题&#xff0c;即可获得本轮优胜。第4题为附加题&#xff0c;对错不影响优胜者…

开源免费图床Lychee本地部署搭建个人云图床并公网访问【内网穿透】

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站&#xff0c;可以看做是云存储的一部分&#xff0c;既可…

作者推荐 |【深入了解系统性能优化】「实战技术专题」全方面带你透彻探索服务优化技术方案(方案分析篇)

全方面带你透彻探索服务优化技术方案 前提背景影响一个系统性能的方方面面代码优化数据库优化网络优化硬件优化 常用的性能评价/测试指标响应时间并发数吞吐量响应时间、并发数和吞吐量之间的关系运作流程关系 性能优化方案的建议避免过早优化进行系统性能测试寻找系统瓶颈&…

R 语言相关资源分享

简介 分享一个 Github 仓库&#xff0c;包含了大量 R 语言教程和资源&#xff0c;旨在帮助各种技能水平和背景的用户深化对 R 的理解。 下面给出一些截图&#xff0c;感兴趣的读者可以自行探索&#xff5e;