哈希表实现(1):

1. 哈希:

之前我们的红黑数的查找是由于左边小右边大的原则可以快速的查找,我们这里的哈希表呢?

这里是用过哈希函数把关键字key和存储位置建立一个关联的映射。

直接定址法(函数函数定义的其中一种):

直接定址法是我们设置哈希函数的第一种方法:

直接定址法的限制就是适用范围比较集中的时候,如果范围不集中的话,我们就不能使用直接定址法。

负载因子:

负载因子其实表示的哈希表的空间的利用率,负载因子越大的时候,空间利用率就越大,哈希冲突的概率就越大,负载因子越小的时候,空间利用率越小,哈希冲突的概率就越小。

要注意这里是概率,不是一定的冲突就大或者小,有可能这批数据刚好比较合适就没有啥冲突。

哈希冲突:

直接定址法是不会有哈希冲突的,直接定址法适用于数据范围比较集中,直接定址法是会给每个值都分配一个空间的。所以不会有哈希冲突。

但是直接定址法的话,我们当然不能一直使用这个,当我们的其他的环境下,我们的数据并不集中的话,我们其实常常使用的是其他的构建哈希函数的方法,他可能会导致几个数据映射到同一个位置上去,导致哈希冲突。

哈希函数:

我们接下来看定义我们的哈希函数的方法:

除法散列法/除留余数法:

除留余数法,我们要让所有的值都映射到M个空间里面,M是哈希表的大小,我们就让关键值key取模M,那么他取模得到的值一定是M-1这个范围里面的某一个,这就映射出了一个位置。

我们看上面的图片,我们看第二点,他说我们的哈希表的大小M要尽量避免2的幂和10的幂。因为我们要计算数据映射的位置的时候,我们是要使用数据来除以哈希表的大小M来得到的。如果使用他们的话就会导致冲突比较大。

除留余数法的话就比直接定址法好多了,我们不需要管数据的范围的大小,我们只要开比数据个数要大的空间就可以。

这就是除留余数法(M是哈希表的大小),19%M得到8,19就映射到8这个位置,然后其他的数据也是一样的,取模M得到一个映射的位置,但是我们的看到30取模M的结果得到的也是8,这个就是我们的哈希冲突。

哈希冲突是不可避免的,我们接下来要讲解如果处理哈希冲突;

我们还有其他的构建哈希函数的方法,乘法散列法和全域散列法,这两个的话我们知道了解一下就行,不进行细讲。

处理哈希冲突:

开放定址法:

当⼀个关键字key⽤哈希函数计算出的位置冲突了,则按照某种规则找到⼀个没有存储数据的位置进⾏存储。简单的说就是我们的这个数据映射出的位置被别人占了,我们就找一个新的位置占上。

这个找新位置的规则我们分成三种:线性探测,二次探测,双重探测。

我们主要学的是线性探测。

线性探测就是映射到这个位置但是这个位置被占了,我们就从这个发生冲突的位置开始,我们就依次的往后走,直到遇到一个空的位置,我们就占到这个位置上(如果走到哈希表的尾了,我们就绕到头上去)。

当我们的映射的这个位置发生冲突被别的数据占据了之后,我们的公式就是给哈希这个位置+1然后继续取模往后走,走到哈希表的尾了,但是我们是取模进行移动的,比如图中的,hash0+i从10变成11的时候,他取模的到的结果就可以跳转回到头部。

开放定址法代码实现:
insert():

Find():

Erase():

素数表:

我们之前说我们要使用素数来设置哈希表的大小,但是如果这个哈希表满了以后,我们需要扩容的时候,我们一般都会给这个哈希表*2来进行扩容,这时候的到的就不是一个素数,可能会导致哈希冲突变大。

我们的库里面就给出了一个不太接近2的素数表。

你看下面的那个是一个lower_bound的函数,这个就是找大于等于的,我们在下面的扩容里面实现的话,我们就可以调用这个素数表的函数,他每次扩容的话,就走到了下一个素数的位置。

当前的size()+1的话,里面的lower_bound是会给你找比这个大或者相等的数据的,假如现在的size()是53,你给他+1,得到54,那么他就会找比54大的数据来进行的。

key不能取模的问题:

我们这里还有一个问题那就是key如果不是我们的int类型的时候,我们建立哈希函数的话,我们是必须要使用除留余数法的,但是如果key不是int类型的,那怎么取模呢?

我们看这个函数,他是绝对插入不到我们的哈希表里面去的,我们的insert函数,我们是要使用到dict.first进行判断的。(这里编译就会报错);

那我们这里怎么解决呢?

我们这里就要走两层映射,首先把string映射成int类型的,然后把int映射到哈希表的位置上。

那怎么把string类型的映射成int类型的数据呢?我们可以把string里面的每一个字母的ASCII值加起来,这个逻辑还比较合适。

那实现怎么来实现呢?

我们就实现一个仿函数来进行我们的转化,

我们先看下面的这个图片:

我们给我们的模板的第三个参数是我们的Hash仿函数,我们给他传一个默认的缺省函数HashFunc函数,这个函数的话,我们会把传进来的类型转为size_t类型的数据,但是有的类型他是转换不成size_t类型的数据的,这时候我们就要自己来手动的实现一个仿函数来进行转换。

这个就是我们实现的仿函数,我们把这个仿函数传进去。

还记得我们之前的仿函数是怎样使用的呢?

我们的仿函数实例化出的对象我们可以直接当作函数来进行使用,看上面Hash仿函数实例化对象hs以后,hs(key)这个就是直接调用仿函数,把key数据转换成int类型的可以取模的数据。

这时候看我们的pair键值对的类型是string类型的,这个类型转成int类型的话,我们就要传我们的自己的仿函数进去才行。

你传其他类型的key也能用,但是的话,你要配一个仿函数类帮助他可以进行取模(必须要能取模,这是构建哈希函数的必要途径)。

我们继续往下看:

我们看,我们刚才自己实现的string转换为int的仿函数,我们是让所有的字母的ASCII加起来,但是这样的话,我们看上面的图片,这三个string的顺序不一样,但是他们的ASCII是一样的,最后导致他们映射的int是同一个,这就导致了冲突,那我们的这个仿函数是不是就显得没有那么好呢?

那有没有刚好的方法来实现这个仿函数,有的,有人提出了BKDRHash方法来进行:

这样我们加起来的ASCII相同的不同顺序的字符串,除非是这两个ASCII值相等的字符串顺序都一样,不然最后计算得到的结果不可能一样。

我们继续往下看:

当我们的容器是我们的unordered_map,这个容器的底层是哈希表实现的,我们给他的key传上string的时候,它不需要仿函数就可以通过运行,但是我们的HashTables我们就要加上仿函数才可以。

要知道我们的string是要经常使用的,经常使用的话,我们想办法让key默认的支持string转化,我们可以使用一个特化来实现。

这个就是特化的实现,当我们的key是string类型的时候,他就是走特化,就不需要仿函数,我们的unordered_map使用的是库里面的,他的底层是由哈希表封装的,库里面已经把这个string的特化实现过了,我们这里调用unordered_map给key传string的话,他就不需要仿函数。

但是我们的这里的哈希表是我们自己在进行实现,我们没有实现特化,我们就要仿函数。

我们看这个特化,特化的上面是我们的仿函数,我们的哈希表可以传各种类型的数据进来,然后我们传仿函数,把各种类型转换为size_t类型的。也可以不传仿函数,把string的特化出来,我们传string类型的数据进来后就直接调用特化的模板了。

现在我们这样就没事,就可以了,我们已经特化了string类型的数据,可以不传仿函数。

我们继续往下看:

我们看这个:

当我们的调用库里面的unordered_map的时候,我们传K传pair<>键值对,这时候也是有问题的,库里面没有实现pair<>键值对的特化,如果想要这种的话,还是需要你手动的实现仿函数。

看这个特化,为了防止你1,3和3,1算出来的值是一样的,减少哈希冲突的发生,使用BKDRHash来实现。

然后接着我们传仿函数进去,然后我们的键值对存1,3和3,1两个数据,实现哈希表的话,这两个数据分别进行存储,我们当然不想让他产生哈希冲突,我们上面的仿函数就使用BKDRHash来实现。

我们看我们的f第二种解决哈希冲突的方法:

链地址法:

这个叫作拉链法,链地址法,这个是非常重要的解决哈希冲突的方法。

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

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

相关文章

泰迪杯特等奖案例深度解析:基于多级二值化与CNN回归的车牌识别系统设计

(第八届泰迪杯数据挖掘挑战赛特等奖案例全流程拆解) 一、案例背景与核心挑战 1.1 行业痛点与场景需求 在智慧交通与无感支付场景中,车牌识别是核心环节。传统车牌识别系统在复杂光照、污损车牌、多角度倾斜等场景下存在显著缺陷。根据某智慧油站2024年运营数据显示,高峰期…

光学变焦和数字变倍模块不同点概述!

一、光学变焦与数字变倍模块的不同点 1. 物理基础 光学变焦&#xff1a;通过调整镜头组中镜片的物理位置改变焦距&#xff0c;实现无损放大。例如&#xff0c;上海墨扬的MF-STAR吊舱采用30倍光学变焦镜头&#xff0c;焦距范围6~180mm&#xff0c;等效焦距可达997mm。 数字…

ECMAScript标准:JavaScript的核心

什么是ECMAScript&#xff1f; ECMAScript&#xff08;简称ES&#xff09;是一个由ECMA国际&#xff08;欧洲计算机制造商协会&#xff09;制定的脚本语言标准&#xff0c;它为JavaScript、JScript和ActionScript等脚本语言提供了基础规范。JavaScript 可以视为 ECMAScript 的…

小白学AI DeepSeep 部署中的常见问题及解决方法

在部署 DeepSeek(或类似的大模型/AI 系统)时,可能会遇到多种技术或环境相关的问题。以下是常见问题及对应的解决方案,结合实际部署经验总结: 文章目录 前言一、 硬件资源不足二、环境配置问题三、模型加载或推理失败四、网络或分布式训练问题五、数据加载或预处理问题六、…

redis数据结构-11(了解 Redis 持久性选项:RDB 和 AOF)

了解 Redis 持久性选项&#xff1a;RDB 和 AOF Redis 提供了多个持久性选项&#xff0c;以确保数据持久性并防止在服务器发生故障或重启时丢失数据。了解这些选项对于为您的特定使用案例选择正确的策略、平衡性能和数据安全至关重要。本章节将深入探讨 Redis 中的两种主要持久…

LLaMA-Factory:环境准备

一、硬件和系统 操作系统: Ubuntu 24.04.2 LTS&#xff08;64位&#xff09;GPU: NVIDIA RTX 4090 笔记本 GPU&#xff0c;16GB显存CPU: 建议高性能多核 CPU&#xff08;如 Intel i7/i9 或 AMD Ryzen 7/9&#xff09;以支持数据预处理&#xff0c;我的是32核。RAM: 至少 32GB&…

2025 uniapp的请求封装工具类以及使用【拿来就用】

一、创建一个http请求封装的js文件&#xff0c;名字自定义&#xff1a;my_http.js /*** 基础API请求地址&#xff08;常量&#xff0c;全大写命名规范&#xff09;* type {string}* constant*/ let BASE_URL //通过环境来判断基础路径 if (process.env.NODE_ENV development…

Qt应用程序启动时的一些思路:从单实例到性能优化的处理方案

程序启动时优化的价值 在桌面软件开发领域,应用程序的启动过程就像音乐的序曲,决定了用户对软件品质的第一印象。比如首次启动等待超过3秒时,会让大多数用户产生负面看法,而专业工具软件的容忍阈值甚至更低。Qt框架作为跨平台开发的利器,其启动过程的优化不仅关乎用户体验…

Node.js入门指南:开启JavaScript全栈开发之旅

Hi&#xff0c;我是布兰妮甜 &#xff01;Node.js让JavaScript突破了浏览器的限制&#xff0c;成为全栈开发的利器。作为基于V8引擎的高性能运行时&#xff0c;它彻底改变了JavaScript只能做前端开发的局面。本文将带你快速掌握Node.js的核心用法&#xff1a;环境搭建与模块系统…

MySQL MCP 使用案例

## 概述 MySQL MCP&#xff08;MySQL Multi-Channel Protocol&#xff09;是MySQL的多通道协议实现&#xff0c;提供了高效的数据库连接池和负载均衡功能。本文档将介绍MySQL MCP的基本使用方法和常见案例。 ## 环境准备 ### 安装MySQL MCP bash pip install mysql-mcp ### 基…

基于 React Hook 封装 Store 的三种方案

基于 React Hook 封装 Store 的三种方案 方案一&#xff1a;基于 useSyncExternalStore 的轻量级 Store&#xff08;推荐&#xff09; import { useSyncExternalStore } from react;type Store<T> {state: T;listeners: Set<() > void>; };function createSt…

MySQL 8.0 OCP 1Z0-908 131-140题

Q131.You have upgraded the MySQL binaries from 5.7.28 to 8.0.18 by using an in-place upgrade. Examine the message sequence generated during the first start of MySQL 8.0.18: 。。。[System]。。。/usx/sbin/mysqld (mysqld 8.0.18-commercial) starting as process…

正向代理和反向代理的区别?

前言 在现代网络架构中&#xff0c;代理服务器扮演着至关重要的角色。无论是企业网络还是互联网服务&#xff0c;代理技术都广泛应用以提高性能、安全性和可管理性。正向代理和反向代理是两种最常见的代理类型&#xff0c;虽然它们都作为中间人处理客户端和服务器之间的通信&am…

技术融资:概念与形式、步骤与案例、挑战与应对、发展趋势

一、技术融资概述 技术融资是指通过外部资金支持技术研发、产品开发或市场扩展的过程。它通常涉及风险投资、天使投资、私募股权、众筹等多种形式。技术融资的核心目标是为技术创新提供资金保障&#xff0c;推动技术从概念到市场的转化。 技术融资的主要形式包括以下几种&…

从硬件角度理解“Linux下一切皆文件“,详解用户级缓冲区

目录 前言 一、从硬件角度理解"Linux下一切皆文件" 从理解硬件是种“文件”到其他系统资源的抽象 二、缓冲区 1.缓冲区介绍 2.缓冲区的刷新策略 3.用户级缓冲区 这个用户级缓冲区在哪呢&#xff1f; 解释关于fork再加重定向“>”后数据会打印两份的原因 4.内核缓冲…

车道线检测----CLRERNet

CLRerNet&#xff1a;利用LaneIoU提升车道检测置信度 摘要 车道标检测在自动驾驶和驾驶辅助系统中至关重要。现代深度车道检测方法在车道检测基准测试中表现出色。通过初步的预言机实验&#xff0c;我们首次拆解车道表示组件以确定研究方向。我们表明&#xff0c;正确的车道位…

ML307R 的 USB Vendor ID (VID):0x2ECC ML307R 的 USB Product ID (PID):0x3012

可以的&#xff0c;在文档的「Table 3. VID、PID查询表」中明确指出&#xff1a; ML307R 的 USB Vendor ID (VID)&#xff1a;0x2ECCML307R 的 USB Product ID (PID)&#xff1a;0x3012 你可以将这对 VID/PID 加到 Linux 的 option 驱动中&#xff0c;比如&#xff1a; ech…

论信息系统项目的范围管理

论信息系统项目的范围管理 前言一、规划范围管理&#xff0c;收集需求二、定义范围三、创建工作分解结构四、确认范围五、控制范围 前言 为了应对烟草零售客户数量大幅度增长所带来的问题&#xff0c;切实履行控烟履约的相关要求&#xff0c;同时也为了响应国务院“放管服”政策…

MongoDB与PostgreSQL两个数据库的特点详细对比

MongoDB 和 PostgreSQL 是两种不同类型的数据库&#xff0c;分别属于 ​​NoSQL&#xff08;文档型&#xff09;​​ 和 ​​关系型&#xff08;SQL&#xff09;​​ 数据库。它们在数据模型、查询语言、扩展性、事务支持等方面有显著差异。以下是详细对比&#xff1a; ​​1. …

计算机网络:什么是电磁波以及有什么危害?

电磁波详解 电磁波(Electromagnetic Wave)是由电场和磁场相互激发、在空间中传播的能量形式。它既是现代通信的基石(如手机、Wi-Fi、卫星信号),也是自然界中光、热辐射等现象的本质。以下从定义、产生、特性、分类及应用全面解析: 一、电磁波的本质 1. 核心定义 电场与…