当前位置: 首页 > news >正文

数据结构(14)——哈希表(1)

欢迎来到博主的专栏:数据结构
博主ID:代码小豪

文章目录

    • 哈希表的思想
    • 映射方法(哈希函数)
      • 除留余数法
    • 哈希表
      • insert
      • 闭散列
      • 负载因子
      • 扩容
      • find和erase

哈希表的思想

在以往的线性表中,查找速度取决于线性表是否有序,如果是无序的线性表,我们就要从表头开始匹配key值是否相同,因此时间复杂度取决于线性表的元素个数,为O(N)。

如果线性表有序,则可以通过二分查找法大大的减少查找时间,时间复杂度为O(logN),这个时间复杂度看起来让人满意,但是我们要考虑到一点,那就是保持线性表的有序性是要付出代价的,使用排序算法也有O(N*logN)的时间复杂度。

即使是使用关联式容器,它的查找速度也是O(logN),但是好处在于插入和删除元素并不会破坏查找速度。但是查找的速度就局限于此了吗?

已现实为例,如果我们想要在城里找到大舅妈,肯定不会遍历整个城的街道,从街道口走到街道尾去找大舅妈的家,我们会先知道大舅妈的住址,直接去这个住址就能找到大舅妈。

这就是哈希表的查找的方式了,将key值放在映射的地址处,这样查找key值就不用从头开始遍历了。举个例子,假设现在有一个能容纳十个元素的哈希表。我们规定key值的个位数就是映射的地址值,即让1位于1号,2位于2号。以此类推,而10则放在0号。

在这里插入图片描述

假设我们要查找13,那么去3号处就能找到对应值,查找速度为O(1)。这就是哈希表查找的优势。

映射方法(哈希函数)

哈希表中的映射方法叫做哈希函数,以上表为例,其哈希函数为F(key)=key%10。于是F(13)=3,F(25)=5。一个好的哈希函数很重要。这将决定哈希表的查找,插入,删除的速率(最主要还是查找)。哈希函数需要能将key值的数据转换成整形的能力。

但是并非所有类型都可以和整形转换。比如string就不能通过F(key)=key%10的方式获得映射值,此时我们就需要设计一个使用于string的哈希函数,比如可以设计让string的所有元素相加为映射值的哈希函数,即F(string)={a1+a2+……+an}。实际上字符串的哈希函数设计绝没这么简单,感兴趣可以在网上搜索。

那么自定义类型当然也要依靠自定义的哈希函数才能获得其映射值,通常哈希函数的设计需要考虑一下几点:
(1)选取的key值的独特性,比如我们在数据库中查找一个人,如果选择“姓名”作为映射值,那么肯定效率不佳,因为全国同名同性的人是在太多了,如果我们选择“生日+姓名”作为映射值就会好很多。

(2)哈希函数的结果要尽可能的分散,假设现在有N个元素要插入到哈希表,那么它们的映射值越分散在哈希表就越好,如果N个元素经过哈希函数的运算结果的映射值都是1,那么肯定是毫无效率可言的。

(3)哈希函数的计算结果要包含在哈希表的域中,如果一个哈希表能容纳100个元素(即可容纳映射值0-99的元素),那么如果一个key值计算出映射值为101,那么肯定是没法插入的。

在c++标准库中的unordered_map,就是用哈希表为底层的容器,其允许我们传入自定义的哈希函数,已适配那些自定义的类型的映射值计算。

template < class Key,                                    // unordered_map::key_typeclass T,                                      // unordered_map::mapped_typeclass Hash = hash<Key>, //可以传入自定义的哈希函数// unordered_map::hasherclass Pred = equal_to<Key>,                   // unordered_map::key_equalclass Alloc = allocator< pair<const Key,T> >  // unordered_map::allocator_type> class unordered_map;

除留余数法

除留余数法是一个比较通用,而且简单的哈希函数。其主要方法为:

设哈希表可容纳最大地址数为m,取一个不超过m的值p作为除数,其哈希函数为:hash(key)=key%p。

比如当前哈希表的最大地址数为50,待插入的key的哈希值为313,则其映射值为313%50=13。

在后续哈希表的底层设计中,博主将采用这个方法。

哈希表

hash表的底层可以用序列式容器vector,或者deque,因为哈希表的映射值与下标很适配。

template<class key,class value>
class hash_tab
{
public:hash_tab(){_tab.resize(10);_n = 0;}typedef pair<key, value> value_type;private:vector<hash_data<value_type>> _tab;size_t _n;//当前有效数据
};

而哈希表每个元素都要存储两个数据,分别是data,以及状态state。由于vector一次性开了十个元素的空间,因此有些空间是没有有效数据的。这些没有有效数据的元素的状态记为空(empty),如果元素具有有效数据则记为有(exist),如果元素的有效数据被删除,则记为删除(delete)。

enum state
{EXIST,//存在映射DELETE,//删除映射EMPTY//空的映射
};template<class T>
struct hash_data
{hash_data(){_state = EMPTY;}hash_data(const T& data,state state){_data = data;_state = state;}const hash_data& operator=(const hash_data& hashdata){_data = hashdata._data;_state = hashdata._state;return *this;}T _data;state _state;
};

insert

insert的方式如下:
(1)先通过hash函数计算出key值的映射地址处。博主采用除留余数法。
(2)将元素插入到映射值对应的地方。

比如现在插入的元素key值为60,由于当前哈希表的最大空间为10(默认构造函数),因此除留余数法为hash(60)=60%10=0。插入在0下标处。
在这里插入图片描述
ok,现在我们来面临第一个问题,如果我们现在插入30,那么它该插入在什么位置呢?根据哈希函数hash(30)=30%10=0。那么它应该插入在映射值为0的下标处,但是0下标处已经存在60这个数据了,那么该如何处理呢?

这种情况被称为“哈希冲突”,即不同的key值(30与60),但是经过哈希函数计算后,得到了相同的映射值(0)。如果想要减少哈希冲突,优化哈希函数是一个不错的选择,但是这并不能解决哈希冲突,通常我们会用两种方法解决哈希冲突,闭散列和开散列。这篇文章我们先来了解闭散列的哈希表,在下一篇中博主再谈谈开散列的哈希表如何实现。

闭散列

闭散列:也称开放地址法,当发生哈希冲突时。探测哈希表的空位置,将key值插入在冲突位置的下一个空位置处。探测方法也分为多种

方法(1)线性探测

线性探测:从冲突位置开始,依次往后探测,直到找到下一个新位置为止。以上例为例,由于30插入的位置与60发生了哈希冲突,那么30从0下标开始线性探测后续位置,直到遇到第一个空位置(EMPTY),DELETE也算作空位置。
在这里插入图片描述
如果此时在插入一个20,与60依然发生了哈希冲突,根据线性探测的方法,20应该插入在下标2的位置。
在这里插入图片描述
假如这个哈希表满了,那么再次插入一个key值会发生什么事呢?
在这里插入图片描述
根据线性探测的规则,插入2的位置会陷入一个死循环,因为在这个哈希表中已经不存在空位置了,在这个哈希表中无论探测多少次都找不到位置。

实际上,当哈希表中的元素占比整个空间越来越多时,哈希冲突发生的几率会越来越高,而每次发生哈希冲突时,都会带来额外的时间开销(线性探测)。因此,最好的解决方法是控制元素与空间之间的占比。

负载因子

我们将哈希表中元素与空间之间的占比成为负载因子。即:

负载因子=元素个数÷哈希表的总容量

负载因子与元素个数成正比,与哈希表的总容量成反比,当负载因子小时,发生哈希冲突的几率低,插入效率高,但是空间利用率也低。当负载因子大时,发生哈希冲突的几率大,插入效率低,但是空间利用率高。因此控制负载因子在一定的数值内也是很重要的。

通常来说,采取线性探测的哈希表,其负载因子应该控制到0.7~0.8。如果负载因子超过这个区间,就要对哈希表进行扩容。以降低哈希表的负载因子。

扩容

哈希表的扩容策略是异地扩容,即先构造一个新的哈希表,这个新的哈希表是原哈希表的二倍,然后再讲原哈希表的元素重新插入在新哈希表当中。最后交换新旧两个哈希表。那么为什么要这样做呢?与其说异地扩容的好处,不如讲讲原地扩容的坏处。

假如我们将上例中的哈希表扩容两倍,即新哈希表的容量为20.
在这里插入图片描述
由于哈希表的最大容量发生了变化,那么哈希函数也会随之变化,因为我们设定的哈希函数为

hash(key)=key%size

由于size从10变成了20,那么哈希函数就变成了key%20。那么哈希表中17的映射值为
hash(17)=17%20=17,这个插入位置就不对。因此更好的方式是新建一个新哈希表。将旧表的内容插入至新表当中。
在这里插入图片描述

	bool insert(const value_type& kv){//负载因子过多,需要扩容 负载因子等于size%_nif (double(_n) / _tab.size() >= 0.7){hash_tab new_tab;new_tab._tab.resize(_tab.size() * 2);//建立新表for (auto& e : _tab){if (e._state == EXIST)//只插入有效值{new_tab.insert(e._data);//将旧表的值插入到新表当中}}_tab.swap(new_tab._tab);//交换新旧两表}size_t hashnum = kv.first % _tab.size();//哈希函数——除留余数法while (_tab[hashnum]._state ==EXIST )//线性探测{hashnum++;hashnum %= _tab.size();}_tab[hashnum] = hash_data<value_type>(kv, EXIST);//插入新值_n++;return true;}

find和erase

find函数的方法如下:

通过哈希函数计算出映射值,找到映射的位置,然后线性检测找到正确的key值,然后返回节点,如果线性检测到空节点(DELETE不算空节点,仅EMPTY)。就说明哈希表中不存在对应key值,返回nullptr。

在这里插入图片描述

	hash_data<value_type>* find(const key& key){size_t hashnum = key % _tab.size();while (_tab[hashnum]._state != EMPTY){if (key == _tab[hashnum]._data.first&&_tab[hashnum]._state!=DELETE){return &_tab[hashnum];}hashnum++;hashnum %= _tab.size();}return nullptr;}

erase的实现就更简单,我们先通过find查找到待删除的元素,然后将该元素的状态调整成DELETE就可以了,因为对这个元素的data进行修改不是一个明智的选择。这会影响到映射关系。


bool erase(const key& key)
{size_t hashnum = key % _tab.size();hash_data<value_type>* ptr=find(key);if (ptr == nullptr)return false;ptr->_state = DELETE;_n--;return true;
}

相关文章:

数据结构(14)——哈希表(1)

欢迎来到博主的专栏&#xff1a;数据结构 博主ID&#xff1a;代码小豪 文章目录 哈希表的思想映射方法&#xff08;哈希函数&#xff09;除留余数法 哈希表insert闭散列负载因子扩容find和erase 哈希表的思想 在以往的线性表中&#xff0c;查找速度取决于线性表是否有序&#…...

K近邻算法_分类鸢尾花数据集

import numpy as np import pandas as pd from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score1.数据预处理 iris load_iris() df pd.DataFrame(datairis.data, columnsiris.featur…...

nacos和eureka的区别详解

Nacos 和 Eureka 都是服务发现和注册中心的解决方案&#xff0c;但它们在功能、设计和使用场景上有所不同。以下是它们的详细区别&#xff1a; 1. 基本概念 Eureka&#xff1a;是由 Netflix 开发的服务发现工具。它主要用于 Java 微服务架构中的服务注册与发现。Eureka 通过 R…...

AI大模型包含哪些些技术?

Prompt Prompt提示是模型接收以生成响应或完成任务的初始文本输入。 我们给AI一组Prompt输入&#xff0c;用于指导模型生成响应以执行任务。这个输入可以是一个问题、一段描述、一组关键词&#xff0c;或任何其他形式的文本&#xff0c;用于引导模型产生特定内容的响应。 Tra…...

分布式技术概览

文章目录 分布式技术1. 分布式数据库&#xff08;Distributed Databases&#xff09;2. 分布式文件系统&#xff08;Distributed File Systems&#xff09;3. 分布式哈希表&#xff08;Distributed Hash Tables, DHTs&#xff09;4. 分布式缓存&#xff08;Distributed Caching…...

动手学习RAG: moka-ai/m3e 模型微调deepspeed与对比学习

动手学习RAG: 向量模型动手学习RAG: moka-ai/m3e 模型微调deepspeed与对比学习动手学习RAG&#xff1a;迟交互模型colbert微调实践 bge-m3 1. 环境准备 pip install transformers pip install open-retrievals注意安装时是pip install open-retrievals&#xff0c;但调用时只…...

Nacos rce-0day漏洞复现(nacos 2.3.2)

Nacos rce-0day漏洞复现&#xff08;nacos 2.3.2&#xff09; NACOS是 一个开源的服务发现、配置管理和服务治理平台&#xff0c;属于阿里巴巴的一款开源产品。影像版本:nacos2.3.2或2.4.0版本指纹&#xff1a;fofa&#xff1a;app“NACOS” 从 Github 官方介绍文档可以看出国…...

yjs04——matplotlib的使用(多个坐标图)

1.多个坐标图与一个图的折线对比 1.引入包&#xff1b;字体&#xff08;同&#xff09; import matplotlib.pyplot as plt import random plt.rcParams[font.family] [SimHei] plt.rcParams[axes.unicode_minus] False 2.创建幕布 2.1建立图层幕布 一个图&#xff1a;plt.fig…...

MOS管和三极管有什么区别?

MOS管是基于金属-氧化物-半导体结构的场效应晶体管&#xff0c;它的控制电压作用于氧化物层&#xff0c;通过调节栅极电势来控制源漏电流。MOS管是FET中的一种&#xff0c;现主要用增强型MOS管&#xff0c;分为PMOS和NMOS。 MOS管的三个极分别是G(栅极)&#xff0c;D(漏极)&…...

医院多参数空气质量监控和压差监测系统简介@卓振思众

在现代医院管理中&#xff0c;确保患者和医疗人员的健康与安全是首要任务。为实现这一目标&#xff0c;医院需要依赖高科技设施来维持最佳的环境条件。特别是&#xff0c;多参数空气质量监测系统和压差监测系统在这一方面发挥了不可替代的作用。【卓振思众】多参数空气质量监测…...

[项目实战]EOS多节点部署

文章总览&#xff1a;YuanDaiMa2048博客文章总览 EOS多节点部署 &#xff08;一&#xff09;环境设计&#xff08;二&#xff09;节点配置&#xff08;三&#xff09;区块信息同步&#xff08;四&#xff09;启动节点并验证同步EOS单节点的环境如何配置 &#xff08;一&#xf…...

setImmediate() vs setTimeout() 在 JavaScript 中的区别

setImmediate() vs setTimeout() 在 JavaScript 中的区别 在 JavaScript 中&#xff0c;setImmediate() 和 setTimeout() 都用于调度任务&#xff0c;但它们的工作方式不同。 JavaScript 的异步特性 JavaScript 以其非阻塞、异步行为而闻名&#xff0c;尤其是在 Node.js 环境…...

【Java文件操作】文件系统操作文件内容操作

文件系统操作 常见API 在Java中&#xff0c;File类是用于文件和目录路径名的抽象表示。以下是一些常见的方法&#xff1a; 构造方法&#xff1a; File(String pathname)&#xff1a;根据给定的路径创建一个File对象。File(String parent, String child)&#xff1a;根据父路径…...

关于若依flowable的安装

有个项目要使用工作流功能&#xff0c;在网上看了flowable的各种资料&#xff0c;最后选择用若依RuoYi-Vue-Flowable这个项目来迁移整合。 一、下载项目代码&#xff1a; 官方项目地址&#xff1a;https://gitee.com/shenzhanwang/Ruoyi-flowable/ 二、新建数据库&#xff…...

猜数字困难版(1-10000)

小游戏&#xff0c;通过提示每次猜高或猜低以及每次猜中的位数&#xff0c;10次内猜中1-10000的一个数。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthde…...

ASPICE术语表

术语来源描述活动Automotive SPICE V4.0由利益相关方或参与方执行的任务用参数Automotive SPICE V4.0应用参数是包含了在系统或软件层级可被更改的数据的软件变量&#xff0c;他们影响系统或软件的行为和属性。应用参数的概念有两种表达方式:规范(分别包括变量名称、值域范围、…...

Knife4j:打造优雅的SpringBoot API文档

1. 为什么需要API文档&#xff1f; 在现代软件开发中,API文档的重要性不言而喻。一份清晰、准确、易于理解的API文档不仅能够提高开发效率,还能降低前后端沟通成本。今天,我们要介绍的Knife4j正是这样一款强大的API文档生成工具,它专为Spring Boot项目量身打造,让API文档的生成…...

数学建模笔记—— 多目标规划

数学建模笔记—— 多目标规划 多目标规划1. 模型原理1.1 多目标规划的一般形式1.2 多目标规划的解1.3 多目标规划的求解 2. 典型例题3. matlab代码实现 多目标规划 多目标规划是数学规划的一个分支。研究多于一个的目标函数在给定区域上的最优化。又称多目标最优化。通常记为 …...

【鸿蒙HarmonyOS NEXT】页面之间相互传递参数

【鸿蒙HarmonyOS NEXT】页面之间相互传递参数 一、环境说明二、页面之间相互传参 一、环境说明 DevEco Studio 版本&#xff1a; API版本&#xff1a;以12为主 二、页面之间相互传参 说明&#xff1a; 页面间的导航可以通过页面路由router模块来实现。页面路由模块根据页…...

SonicWall SSL VPN曝出高危漏洞,可能导致防火墙崩溃

近日&#xff0c;有黑客利用 SonicWall SonicOS 防火墙设备中的一个关键安全漏洞入侵受害者的网络。 这个不当访问控制漏洞被追踪为 CVE-2024-40766&#xff0c;影响到第 5 代、第 6 代和第 7 代防火墙。SonicWall于8月22日对其进行了修补&#xff0c;并警告称其只影响防火墙的…...

关于SAP标准委外(带料外协)采购订单信息

业务背景&#xff1a; 业务部门提出需要将售料外协方式变更为带料外协&#xff0c;带料外协实际业务存在一个委外订单存在多次发料&#xff0c;且每次发票需要进行齐套发料&#xff0c;不同批次的发料涉及物料替代。在半成品收货时需要进行对发料的组件进行扣料。 需求分析&a…...

SpringBoot整合WebSocket实现消息推送或聊天功能示例

最近在做一个功能&#xff0c;就是需要实时给用户推送消息&#xff0c;所以就需要用到 websocket springboot 接入 websocket 非常简单&#xff0c;只需要下面几个配置即可 pom 文件 <!-- spring-boot-web启动器 --><dependency><groupId>org.springframewo…...

使用 QEMU 模拟器运行 FreeRTOS 实时操作系统

文章目录 QEMU 官网QEMU 文档QEMU 简介QEMU 安装QEMU 命令启动虚拟机串口控制台监控命令行 FreeRTOS安装编译工具FreeRTOS 源码RISC-V-Qemu-virt_GCC 示例编译 RISC-V-Qemu-virt_GCC启动虚拟机运行 FreeRTOS QEMU 官网 https://www.qemu.org/ QEMU 文档 https://www.qemu.or…...

Oracle EBS中AR模块的财务流程概览

应收账款 (AR) 模块是Oracle E-Business Suite (EBS) 中另一个重要的财务管理模块&#xff0c;主要用于管理企业销售过程中的账款回收。下面是AR模块中的一些关键财务流程及其详细说明&#xff1a; 1. 销售订单管理 创建销售订单&#xff1a;当客户下单时&#xff0c;销售人员…...

Minitab 的直方图结果分析解释

Minitab 的直方图结果分析解释 步骤 1&#xff1a;评估关键特征 检查分布的尖峰和散布。评估样本数量对直方图外观的影响。 标识尖峰&#xff08;即&#xff0c;条的最高聚类&#xff09;&#xff1a; 尖峰表示样本中最常见的值。评估样本的散布以了解数据的变异程度。例如…...

AgentRE:用智能体框架提升知识图谱构建效果,重点是开源!

发布时间&#xff1a;2024 年 09 月 13 日 Agent应用 AgentRE: An Agent-Based Framework for Navigating Complex Information Landscapes in Relation Extraction 在复杂场景中&#xff0c;关系抽取 (RE) 因关系类型多样和实体间关系模糊而挑战重重&#xff0c;影响了传统 “…...

力扣题解2390

大家好&#xff0c;欢迎来到无限大的频道。 今日继续给大家带来力扣题解。 题目描述​&#xff08;中等&#xff09;&#xff1a; 从字符串中移除星号 给你一个包含若干星号 * 的字符串 s 。 在一步操作中&#xff0c;你可以&#xff1a; 选中 s 中的一个星号。 移除星号…...

用Python获取PDF页面的大小、方向和旋转角度

在文档管理和自动化领域&#xff0c;了解PDF文档的内在属性&#xff08;如页面大小、方向和旋转角度&#xff09;对于确保一致的文档处理和布局保真度至关重要。这些属性在内容重用、归档以及PDF无缝集成到网络环境或其他数字工作流程中起着关键作用&#xff0c;因为它们直接影…...

【即时通讯】轮询方式实现

技术栈 LayUI、jQuery实现前端效果。django4.2、django-ninja实现后端接口。 代码仓 - 后端 代码仓 - 前端 实现功能 首次访问页面并发送消息时需要设置昵称发送内容为空时要提示用户不能发送空消息前端定时获取消息&#xff0c;然后展示在页面上。 效果展示 首次发送需要…...

Flock 明牌空投教程

FLock 旨在为人工智能构建一个去中心化的隐私保护解决方案。FLock提出了一项名为联合学习区块&#xff08;简称 FLocks&#xff09;的研究计划&#xff0c;该计划使用区块链作为数据持有者之间的协调平台来进行机器学习&#xff0c;同时数据保持本地和隐私。通过用区块链取代收…...