【C++学习篇】哈希表的实现

devtools/2025/1/21 3:04:56/

目录

1.哈希的概念

1.1 直接定址法

1.1.1 例题 字符串中的第一个唯一字符

1.2 哈希函数

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

1.2.2  乘法散列法

1.3 哈希冲突

1.4 负载因子 

1.5 处理哈希冲突

1.5.1 开放定址法

1.5.1.1 线性探测 

1.5.1.2 二次探测

1.5.1.3 双重探测 



1.哈希的概念

哈希(hash)⼜称散列,是⼀种组织数据的⽅式。从译名来看,有散乱排列的意思。本质就是通过哈希函数把关键字Key跟存储位置建⽴⼀个映射关系,查找时通过这个哈希函数计算出Key存储的位置,进⾏快速查找。

注意!!!哈希表不代表就是哈希,哈希表只是哈希衍生出来的一个一个东西。

 那接下来,我开始介绍哈希思想

 

1.1 直接定址法

当关键字的范围⽐较集中时,直接定址法就是⾮常简单⾼效的⽅法,⽐如⼀组关键字都在[0,99]之间,那么我们开⼀个100个数的数组,每个关键字的值直接就是存储位置的下标。也就是说直接定址法本质就是⽤关键字计算出⼀个绝对位置或者相对位置。
 

1.1.1 例题 字符串中的第一个唯一字符

一道题让你明白直接定址法-》力扣--字符串中的第一个唯一字符icon-default.png?t=O83Ahttps://leetcode.cn/problems/first-unique-character-in-a-string/description/

 

但是,直接定址法,只适合元素范围比较集中的整形或者字符。像浮点数,字符串这些,就处理不了 。所以这里我们需要引出一个东西,就是哈希函数,通过哈希函数,将 关键字Key跟存储位置建⽴⼀个映射关系。

 

1.2 哈希函数

⼀个好的哈希函数应该让N个关键字被等概率的均匀的散列分布到哈希表的M个空间中,但是实际中却很难做到,但是我们要尽量往这个⽅向去考量设计。

 

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

 1.  除法散列法也叫做除留余数法,顾名思义,假设哈希表的⼤⼩为M,那么通过key除以M的余数作为映射位置的下标,也就是哈希函数为:h(key) = key % M。


2.  当使⽤除法散列法时,要尽量避免M为某些值,如2的幂,10的幂等。如果是 2^X ,那么key % 2^X本质相当于保留key的后X位,那么后x位相同的值,计算出的哈希值都是⼀样的,就冲突了。如:{63 , 31}看起来没有关联的值,如果M是16,也就是 2^4 ,那么计算出的哈希值都是15,因为63的⼆进制后8位是 00111111,31的⼆进制后8位是 00011111。


3.  当使⽤除法散列法时,建议M取不太接近2的整数次冥的⼀个质数(素数)。
4.  需要说明的是,实践中也是⼋仙过海,各显神通,Java的HashMap采⽤除法散列法时就是2的整数次冥做哈希表的⼤⼩M,这样玩的话,就不⽤取模,⽽可以直接位运算,相对⽽⾔位运算⽐模更⾼效⼀些。但是他不是单纯的去取模,⽐如M是2^16次⽅,本质是取后16位,那么⽤key’ =
ey>>16,然后把key和key' 异或的结果作为哈希值。也就是说我们映射出的值还是在[0,M)范围内,但是尽量让key所有的位都参与计算,这样映射出的哈希值更均匀⼀些即可。所以我们上⾯建议M取不太接近2的整数次冥的⼀个质数的理论是⼤多数数据结构书籍中写的理论吗,但是实践中,灵活运⽤,抓住本质,⽽不能死读书。

 

1.2.2  乘法散列法

 

 1.2.3 全域散列法

 

1.3 哈希冲突

直接定址法的缺点也⾮常明显,当关键字的范围⽐较分散时,就很浪费内存甚⾄内存不够⽤。假设我们只有数据范围是[0, 9999]的N个值,我们要映射到⼀个M个空间的数组中(⼀般情况下M >= N),那么就要借助哈希函数(hash function)hf,关键字key被放到数组的h(key)位置,这⾥要注意的是h(key)计算出的值必须在[0, M)之间。

这⾥存在的⼀个问题就是,两个不同的key可能会映射到同⼀个位置去,这种问题我们叫做哈希冲突,或者哈希碰撞。理想情况是找出⼀个好的哈希函数避免冲突,但是实际场景中,冲突是不可避免的,所以我们尽可能设计出优秀的哈希函数,减少冲突的次数,同时也要去设计出解决冲突的⽅案
 

1.4 负载因子 

假设哈希表中已经映射存储了N个值,哈希表的⼤⼩为M,那么 ,负载因⼦=N/M,有些地⽅也翻译为载荷因⼦/装载因⼦等,他的英⽂为load factor。负载因⼦越⼤,哈希冲突的概率越⾼,空间利⽤率越⾼;负载因⼦越⼩,哈希冲突的概率越低,空间利⽤率越低;

 

1.5 处理哈希冲突

1.5.1 开放定址法

在开放定址法中所有的元素都放到哈希表⾥,当⼀个关键字key⽤哈希函数计算出的位置冲突了,则按照某种规则找到⼀个没有存储数据的位置进⾏存储,开放定址法中负载因⼦⼀定是⼩于的。这⾥的规则有三种:线性探测、⼆次探测、双重探测。
 

1.5.1.1 线性探测 


1.5.1.2 二次探测

 

 

1.5.1.3 双重探测 

 

1.5.2 链地址法 

 扩容

开放定址法负载因⼦必须⼩于1,链地址法的负载因⼦就没有限制了,可以⼤于1。负载因⼦越⼤,哈希冲突的概率越⾼,空间利⽤率越⾼;负载因⼦越⼩,哈希冲突的概率越低,空间利⽤率越低;stl中unordered_xxx的最⼤负载因⼦基本控制在1,⼤于1就扩容,我们下⾯实现也使⽤这个⽅式。


http://www.ppmy.cn/devtools/152249.html

相关文章

使用Go语言中的Buffer实现高性能处理字节和字符串

文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…

golang标准库path/filepath使用示例

文章目录 前言一、常用方法示例1.将相对路径转换为绝对路径2.获取路径中最后一个元素3.获取路径中除去最后一个元素的部分4.路径拼接5.将路径拆分为目录和文件名两部分6.返回一个相对路径7.文件路径遍历8.根据文件扩展名过滤文件9.使用正则表达式进行路径匹配 前言 path/filep…

如何将本地电脑上的文件夹设置为和服务器的共享文件夹

将本地电脑上的文件夹设为与服务器共享的文件夹,通常是在本地开启文件共享,并配置相应的权限,使服务器可以访问该文件夹。以下以 Windows 系统为例说明具体操作步骤: 一、在本地电脑上设置共享文件夹 选择文件夹 找到需要共享的文…

ARM学习(42)CortexM3/M4 MPU配置

笔者之前学习过CortexR5的MPU配置,现在学习一下CortexM3/M4 MPU配置 1、背景介绍 笔者在工作中遇到NXP MPU在访问异常地址时,就会出现总线挂死,所以需要MPU抓住异常,就需要配置MPU。具体背景情况可以参考ARM学习(41)NXP MCU总线挂死,CPU could not be halted以及无法连…

广东打造低空经济发展平台,CES Asia 2025助力科技腾飞

在2025年广东省政府工作报告中明确提出,将打造“13N”低空经济发展平台,致力于完善低空智慧物流、城市空中交通、航空应急救援等体系,这一举措标志着广东在低空经济领域的雄心壮志。 广东作为全国经济强省,在低空经济领域已经拥有…

Node.js 版本管理工具完全指南

Node.js 版本管理工具完全指南 目录 1. nvm (Node Version Manager)2. n (Node Package Manager)3. fnm (Fast Node Manager)4. Volta5. 工具对比 1. nvm (Node Version Manager) 1.1 安装指南 macOS/Linux # 使用 curl 安装 curl -o- https://raw.githubusercontent.com…

一文大白话讲清楚webpack基本使用——2——css相关loader的配置和使用

一文大白话讲清楚webpack基本使用——2——css相关loader的配置和使用 1. 建议按文章顺序从头看是看 第一篇:一文大白话讲清楚啥是个webpack第二篇:一文大白话讲清楚webpack基本使用——1——完成webpack的初步构建然后看本篇,Loader的配置…

【Rust自学】13.8. 迭代器 Pt.4:创建自定义迭代器

13.8.0. 写在正文之前 Rust语言在设计过程中收到了很多语言的启发,而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。 在本章中,我们会讨论 Rust 的一…