哈希表数据结构学习

news2024/11/15 23:51:14

哈希表数据结构学习

  • 哈希表
    • 基本概念
    • 哈希方法
    • 单值哈希与多值哈希
    • 哈希冲突
      • 1. 开放寻址法(Open Addressing)
      • 2. 链地址法(Chaining)
      • 3. 再哈希法(Rehashing)
      • 4. 建立公共溢出区(Overflow Area)
      • 5. 扩展线性哈希法(Extendible Hashing)
      • 6. 线性分离法(Cuckoo Hashing)
    • 空间扩容
      • vector扩容
      • 负载因子以及增容
    • GPU上的哈希表

哈希表

这里不区分 hashmap 和 hash table,(个人理解)一般hashmap指哈希表这种数据结构,而hash table指通过这种数据结构建立所得的结果。

哈希表,又称散列表,它通过建立键 key 与值 value 之间的映射,实现高效(O(1) )的元素查询。

为什么哈希又叫散列——其实一个是音译一个是意译。
散列(hash)英文原意是“混杂”、“拼凑”、“重新表述”的意思。

在哈希表中进行增删查改的时间复杂度都是 O(1) 。
查找某个元素是否存在的过程中,数组和链表都需要挨个循环比较,而通过 哈希 计算,可以大大减少比较次数。
在这里插入图片描述

基本概念

若键key或k,其值value或v存放在f(k)对应的桶bucket中。映射关系f为哈希函数或散列函数,所得的表成为哈希表或散列表。

哈希映射由一个桶数组组成,其中每个桶可以包含一个或多个键值对。要在映射中插入新的对,将向键应用哈希函数以生成哈希值。然后使用该哈希值选择其中一个桶。如果存储桶可用,则该对存储在该存储桶中。

例如,要插入键值对 (Alice, 408-555-0148) ,对键,进行散列以获取其散列值( hash(Alice)=4),并选择位置 4 处的存储桶来存储值(408-555-0148)。稍后,要检索与 Alice 关联的值,可以使用相同的哈希函数 hash(Alice), 再次选择位置 4 处的存储桶并检索先前存储的值。

哈希方法

  • 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即hash(k)=k或hash(k)=ak+b(a,b为常数),(这种散列函数叫做自身函数)。

在LeetCode中常用一维数组

  • 数字分析法:假设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
  • 平方取中法:取关键字平方后的中间几位为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,取其中的哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定。
    折- 叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
  • 除留余数法:取关键字 key 对某个不大于哈希表长度的质数 p 取余数作为哈希地址,即 hash(key) = key % p。选择质数可以减少哈希冲突。
  • 位运算法:通过对关键字进行位移、异或、与或等位运算来生成哈希值。这种方法速度快且实现简单,尤其适合于二进制数据。
  • 混合哈希法等等

密码学中常见的哈希算法

  • MD5 (Message-Digest Algorithm 5)
    特点:MD5 是一种广泛使用的哈希函数,它将任意长度的输入数据映射为 128 位的散列值(通常表示为 32 位的十六进制数)。
    应用:MD5 常用于数据完整性校验。但是,由于存在碰撞攻击(即不同的输入会生成相同的哈希值),MD5 在加密应用中已被逐步淘汰。

  • SHA-2 (Secure Hash Algorithm 2)
    特点:SHA-2 是 SHA-1 的改进版,具有更强的安全性。SHA-2 包括多个不同的变种,如 SHA-224、SHA-256、SHA-384、SHA-512 等,分别生成 224 位、256 位、384 位和 512 位的散列值。
    应用:SHA-2 广泛应用于数字签名、TLS/SSL 证书和区块链技术中。

单值哈希与多值哈希

讨论哈希表时的一个重要区别是是否允许重复键。

  • 单值哈希表或哈希映射要求键是唯一的(例如 std::unordered_map),单键对单值。
  • 多值哈希表和哈希多映射允许重复键(如 std::unordered_multimap),单键对多值。

哈希冲突

如果表中桶的数量等于可能的键的数量,则可以使用哈希桶和键之间的一对一关系,其中每个键正好映射到表中的一个桶。

然而,这在大多数情况下是不切实际的,因为潜在键值对的数量事先不知道,或者为每个键值对保留存储桶所需的存储将超过可用的存储容量。好比一个电话簿不可能写得下宇宙中每个可能的名字和其号码。

因此,哈希函数通常是不完美的,并可能导致哈希冲突(Hash Collision),其中两个不同的键映射到相同的哈希值( f ( k 1 ) = f ( k 2 ) , k 1 ≠ k 2 f(k_1)=f(k_2),k_1\neq k_2 f(k1)=f(k2),k1=k2)。好的哈希函数寻求最小化冲突的可能性,但在大多数情况下它们是不可避免的。

在这里插入图片描述

在哈希表中,由于不同的关键字可能被映射到相同的哈希地址,导致哈希冲突(Hash Collision)。为了解决哈希冲突,可以采用以下几种常见的方法:

1. 开放寻址法(Open Addressing)

当发生哈希冲突时,开放地址法通过在哈希表中找到另一个空闲位置来存储冲突的关键字。常见的开放地址法有以下几种策略:

  • 线性探测法(Linear Probing) :当发生冲突时,按顺序检查哈希表的下一个位置,直到找到一个空闲位置。
    优点 :实现简单,容易理解。
    缺点 :容易形成“聚集”现象,导致性能下降。
  • 二次探测法(Quadratic Probing) :探测位置不是线性增长,而是按二次函数增长,即探测序列为 hash(key) + 1², hash(key) + 2², ...
    优点 :减少了线性探测法中的聚集问题。
    缺点 :探测序列可能导致访问不到某些位置,尤其是当哈希表大小不是质数时。
  • 双重哈希法(Double Hashing) :使用两个不同的哈希函数 h1(key)h2(key),当发生冲突时,探测序列为 h1(key) + i * h2(key)
    优点 :减少了冲突发生的概率,更均匀地分布关键字。
    缺点 :实现相对复杂,需要设计两个合适的哈希函数。

2. 链地址法(Chaining)

每个哈希表槽位对应一个链表,当多个关键字被映射到同一槽位时,将这些关键字存储在链表中。
优点 :

  • 容易处理不同大小的哈希表。
  • 无需担心哈希表装载因子过大(元素个数超出哈希表大小)。
    缺点 :
  • 链表的使用增加了额外的内存开销。
  • 查找和删除操作的时间复杂度取决于链表的长度,最坏情况下为O(n)。

3. 再哈希法(Rehashing)

当发生冲突时,使用一个新的哈希函数重新计算哈希地址,直到找到一个空闲的位置。
优点 :可以减少冲突的发生,提高哈希表的查找效率。
缺点 :需要设计多个有效的哈希函数,增加了实现复杂性。

4. 建立公共溢出区(Overflow Area)

将冲突的关键字存放在一个公共的溢出区中,而不是在原哈希表中继续查找位置。
优点 :

  • 简化了哈希表的查找逻辑,尤其是当溢出区结构简单时。
    缺点 :
  • 溢出区可能导致查找效率低下,尤其是在溢出区中的元素较多时。
  • 需要额外的内存来存储溢出区。

5. 扩展线性哈希法(Extendible Hashing)

基于二进制哈希值的扩展哈希表,每次冲突会通过增加哈希表的位数来解决冲突。哈希表会随着数据的增加而动态扩展。
优点 :

  • 动态扩展,避免了固定哈希表大小的限制。
  • 分布均匀,减少了冲突的概率。
    缺点 :实现较为复杂,适合需要动态调整哈希表大小的场景。

6. 线性分离法(Cuckoo Hashing)

使用两个哈希函数和两个哈希表,当插入时,如果第一个位置已经被占用,则将占用该位置的元素移动到另一个哈希表中,循环此操作直到没有冲突或达到预设的最大尝试次数。
优点 :

  • 查找时间复杂度为 O(1),因为每个元素最多只能在两个位置上找到。
  • 插入操作复杂度较低。
    缺点 :
  • 可能会出现无限循环,需要重新哈希。
  • 需要更大的哈希表空间来避免频繁的冲突。

每种方法都有其适用的场景和局限性,选择合适的哈希冲突解决方法取决于具体应用的需求、哈希表的大小以及允许的冲突处理开销。

空间扩容

由于哈希表能够直接访问查找元素的地址,所以它的时间复杂度为常数的复杂度 O(1)。而每一个key到address的映射关系需要记录下来,假设哈希表有 n 个元素,那么就需要 n 条记录,故空间复杂度为 O(n)。

关于复杂度记法
Ο,读音:big-oh;表示上界,小于等于。
Ω,读音:big omega、欧米伽;表示下界,大于等于。
Θ,读音:theta、西塔;既是上界也是下界,称为确界,等于。
ο,读音:small-oh;表示上界,小于。
ω,读音:small omega;表示下界,大于。
Ο是渐进上界,Ω是渐进下界。Θ需同时满足大Ο和Ω,故称为确界。Ο极其有用,因为它表示了最差性能。

vector扩容

先看以下vector是怎么扩容的。

vector容器不同于数组,能够进行动态扩容,其底层原理:所谓动态扩容,并不是在原空间之后接续新空间,因为无法保证原空间之后尚有可配置的空间。而是以原大小的两倍另外配置一块较大空间,然后将原内容拷贝过来,并释放原空间。

在这里插入图片描述

push_back扩容机制:当push_back一个元素时,

如果发现size() == capacity(),那么会以两倍空间扩容,然后将元素插入到finish迭代器的下一个元素(注意会申请一个新的空间,并将原有元素拷贝到新空间中,然后释放原有空间)
如果发现size() < capacity(),那么会插入到finish迭代器的下一个元素
不会出现size() > capacity()

pop_back、earse、clear缩容机制
pop_back会减少一个size(),但是不会改变capacity() (finish迭代器前移一位)
earse会减少一个size(),但是不会改变capacity() (finish迭代器前移一位)
clear令size()为0,但是不会改变capacity()(将finish迭代器移动到start相同位置)

对于resize(new_size)
如果new_size== curr.size,什么也不做
如果new_size< curr.size, 那么 curr.size = new_size,curr.capacity不变
如果new_size> curr.size, 那么 curr.size = new_size,curr.capacity = new_size,将容器capacity 扩大到能容纳new_size的大小,改变容器的curr.size,并且创建对象。

对于reserve(new_size)
如果new_size== curr.size,什么也不做
如果new_size< curr.size,什么也不做
如果new_size> curr.size,curr.size不变,curr.capacity=new_size,将容器capacity 扩大到能容纳new_size的大小,在空间内不真正创建对象,所以不改变curr.size

真正的释放内存

vector<int>(v).swap(v); 

vector(v)通过拷贝构造函数创建了一个匿名对象,这个匿名对象拥有v的全部数据,但是,没有空闲的空间,也就是说,这个匿名对象的容量和数据量是相等的。

此时,通过 swap(v) 调用该匿名对象的swap()方法,交换v与匿名对象的内容。

匿名对象在执行完代码之后会自动调用析构函数,那么空间被释放,最终结果就是,原容器中的空位被释放。

在这里插入图片描述

负载因子以及增容

若哈希冲突出现的较为密集,往往代表着此时数据过多,而能够映射的地址过少,而要想解决这个问题,就需要通过 负载因子(装填因子) 的判断来进行增容

负载因子的大小 = 表中数据个数 / 表的容量(长度)

闭散列
对于闭散列来说,因为其是一种线性的结构,所以一旦负载因子过高,就很容易出现哈希冲突的堆积,所以当负载因子达到一定程度时就需要进行增容,并且增容后,为了保证映射关系,还需要将数据重新映射到新位置。对应上述开放寻址法。

闭散列是一种预先分配存储空间的方法。在闭散列中,存储空间被划分为若干个固定大小的桶,每个桶中可以存储多个元素。当插入新元素时,根据其哈希值确定其所属的桶,并将元素添加到该桶中。如果发生哈希冲突,则将元素添加到下一个可用的桶中。这种方法简单易行,但可能会导致某些桶被过度使用,而其他桶仍空闲。
开散列是一种动态调整存储空间的方法。在开散列中,当某个桶已满时,会根据一定规则分配一个新的桶。这种方法可以更好地利用存储空间,但需要额外的空间来管理桶的分配。
在实际应用中,可以根据具体情况选择不同的哈希策略。例如,对于需要快速插入和查找的数据结构,闭散列可能是一个更好的选择。而对于需要高效利用存储空间的数据结构,开散列可能更为合适。

经过算法科学家的计算, 负载因子应当严格的控制在 0.7-0.8 以下,所以一旦负载因子到达这个范围,就需要进行增容。

因为除留余数法等方法通常是按照表的容量来计算,且当对一个质数取模时,冲突的几率会大大的降低,并且因为增容的区间一般是 1.5-2 倍,所以算法科学家列出了一个增容质数表,按照这样的规律增容,冲突的几率会大大的降低。
这也是 STL 中 unordered_map/unordered_set 使用的增容方法

开散列
因为哈希桶是开散列的链式结构,发生了哈希冲突是直接在对应位置位置进行头插,而桶的个数是固定的,而插入的数据会不断增多,随着数据的增多,就可能会导致某一个桶过重,使得效率过低。对应上述拉链法。

在这里插入图片描述
开散列中每个桶放的都是哈希冲突的元素。哈希桶下面挂着的是一个一个的节点(一条链表),如果该位置哈希冲突的元素过多时,通常会将这条链表转为一颗红黑树。


GPU上的哈希表

精心设计的哈希函数通过最大化哈希任意两个键将导致不同哈希值的可能性来最小化冲突次数。这意味着对于任何给定的两个键,它们对应的桶可能位于不同的内存位置。

因此,大多数哈希表操作的内存访问模式实际上是随机的。为了理解哈希表的性能,了解随机内存访问的性能非常重要。

下表比较了理论峰值带宽与在现代 GPUs 和 GPUs 上通过 GUPs benchmark 测量的随机 64 位读取的实现带宽(带宽计算为访问大小乘以访问次数除以时间)。

在这里插入图片描述
随机内存访问大约比理论峰值带宽慢 10 倍。这是因为内存子系统针对顺序访问进行了优化。更重要的是, NVIDIA GPU s 的随机访问吞吐量比现代 CPU s 的高一个数量级。这些结果表明,性能最好的 CPU 哈希表可能比性能最好的 GPU 哈希表慢一个数量级

显存允许的情况下那肯定优先考虑使用GPU哈希表了。

以下是一个CUDA的哈希表三方库:
在这里插入图片描述

在这里插入图片描述


参考

  • maximizing-performance-with-massively-parallel-hash-maps-on-gpus
  • hello-algo/hash_map
  • C++进阶(哈希)

先到这儿。

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

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

相关文章

OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3568移植案例(下)

往期知识点记录&#xff1a; OpenHarmony&#xff08;鸿蒙南向开发&#xff09;——轻量系统STM32F407芯片移植案例 OpenHarmony&#xff08;鸿蒙南向开发&#xff09;——Combo解决方案之W800芯片移植案例 OpenHarmony&#xff08;鸿蒙南向开发&#xff09;——小型系统STM32M…

『功能项目』窗口可拖拽脚本【59】

本章项目成果展示 我们打开上一篇58第三职业弓弩的平A的项目&#xff0c; 本章要做的事情是给坐骑界面挂载一个脚本让其显示出来的时候可以进行拖拽 创建脚本&#xff1a;DraggableWindow.cs using UnityEngine; using UnityEngine.EventSystems; public class DraggableWindo…

使用three.js+vue3完成无人机上下运动

效果图如上 代码&#xff1a; <template><div class"drones"><div ref"dronesContainer" class"drones-container"></div></div></template><script setup>import { ref, onMounted, onUnmounted, …

性能再升级,华为Mate 70 Pro曝光,设计新颖且配置遥遥领先

在智能手机市场竞争日益激烈的今天&#xff0c;各大厂商都在努力提升自家产品的性能和设计。 华为作为中国领先的手机品牌&#xff0c;一直备受关注。 近日&#xff0c;有关华为Mate 70 Pro的曝光信息引发了广泛关注&#xff0c;据悉&#xff0c;这款新机将在性能、设计和配置…

vue和thinkphp路由伪静态配置

vue路由伪静态配置&#xff1a; location / { try_files $uri $uri/ /index.html; } thinkphp 路由伪静态配置 location ~* (runtime|application)/{ return 403; } location / { if (!-e $request_filename){ rewrite ^(.*)$ /index.php?s$1 last; break; } }

【Java】基础语法介绍

目录 一、注释 二、标识符与关键字 三、输入和输出 3.1 输出 3.2 输入 四、数据类型 3.1 基本数据类型 3.2 引用数据类型 3.3 var关键字 五、运算符 六、分支和循环 5.1 分支 5.2 循环 七、类和对象 6.1 类的定义与对象的创建 6.2 空对象 6.3 类的属性 6.4 类…

优化下载性能:使用Python多线程与异步并发提升下载效率

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 普通请求下载📝 使用多线程加速下载📝 使用异步编程加速下载📝 总结 📝⚓️ 相关链接 ⚓️📖 介绍 📖 你是否因为下载速度慢而感到焦虑?特别是在下载大型文件时,等待进度条慢慢移动的感觉真的很…

【Head-DETR系列(7)】DETR 代码分析

在nuscens数据集上&#xff0c; Results and Models BackboneModelLr schdMem (GB)Inf time (fps)box APConfigDownloadR-50DETR150e7.940.1configmodel | log 我们先看检测器 /mmdetection-2.28.2/mmdet/models/detectors/detr.py def forward_train(self,img,img_metas,gt_…

利基网站收入报告(更新至十月)

欢迎来到我的利基网站收入报告。这是我揭露当月我所有网站收入情况的地方。目前&#xff0c;我主要专注于一个核心网站&#xff0c;其余的不是重心&#xff0c;有些可能会在不久的将来出售。 为什么我分享我的利基网站收入报告&#xff1f; 需要旧报告&#xff1f; 2023年10月…

JSON包新提案:用“omitzero”解决编码中的空值困局

Go标准库是Go号称“开箱即用”的重要因素&#xff0c;而标准库中的encoding/json包又是标准库最常用的Go包。虽然其性能不是最好的&#xff0c;但好在由Go团队维护&#xff0c;对JSON规范兼容性好&#xff0c;且质量很高。但json包也不是没有“瑕疵”的&#xff0c;Go官方继mat…

6款SSL证书实用工具,格式转换/CSR生成等全都免费使用!

俗话说“工欲善其事&#xff0c;必先利其器”&#xff0c;SSL证书作为保护网站数据传输安全的重要部分&#xff0c;我们在申请、签发、部署安装SSL证书的时候&#xff0c;可能会涉及到CSR文件生成、获取证书链、转换证书格式等需求&#xff0c;这时候有对应的工具就可提高工作效…

基于SpringBoot的考研助手系统+LW参考示例

系列文章目录 1.基于SSM的洗衣房管理系统原生微信小程序LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统LW参考示例 3.基于SpringBootVue的企业人事管理系统LW参考示例 4.基于SSM的高校实验室管理系统LW参考示例 5.基于SpringBoot的二手数码回收系统原生微信小程序LW参考示…

干货| Python代码性能优化总结

代码优化原则 本文会介绍不少的 Python 代码加速运行的技巧。在深入代码优化细节之前&#xff0c;需要了解一些代码优化基本原则。 这里插播一条粉丝福利&#xff0c;如果你正在学习Python或者有计划学习Python&#xff0c;想要突破自我&#xff0c;对未来十分迷茫的&#xf…

超全网络安全面试题汇总(2024版)

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

P1079 [NOIP2012 提高组] Vigenère 密码------------------------------P1703 那个什么密码2

P1079 [NOIP2012 提高组] Vigenre 密码 题目描述 16 世纪法国外交家 Blaise de Vigenre 设计了一种多表密码加密算法 Vigenre 密码。Vigenre 密码的加密解密算法简单易用&#xff0c;且破译难度比较高&#xff0c;曾在美国南北战争中为南军所广泛使用。 在密码学中&#xff…

3DMAX道路生成器插件RoadGenerator使用方法详解

3DMAX道路生成器插件RoadGenerator&#xff0c;一键生成全模3DMax道路插件&#xff0c;是一款便捷且极受欢迎的参数化道路建模插件。RoadGenerator插件从样条线&#xff08;道路中心线&#xff09;快速创建道路系统。包括路面、行车线、双黄线、斑马线、箭头、路牙、人行道、路…

2024年某大厂HW蓝队面试题分享

&#x1f91f; 基于入门网络安全/黑客打造的资源包无偿分享中&#xff1a; &#x1f449;黑客&网络安全入门&进阶学习资源包 应急响应流程 1&#xff09;首先判断服务器资产、影响范围以及严重程度&#xff0c;确认有没有必要将服务器下线隔离&#xff0c;然后根据服务…

YOLOv8模型实时检测RTSP协议视频流并实时发送报警信息到Java服务端实现(超详细)

前言 在训练模型完成后&#xff0c;想把模型应用起来&#xff0c;比如模型可以部署到项目中&#xff0c;实时接收RTSP视频流进行识别检测&#xff0c;一旦达到自己所设置的置信度阈值&#xff08;例如大于0.5&#xff09;&#xff0c;系统就会实时把报警信息发送给服务端&…

Linux抢占调度

目录 抢占流程 抢占时机 用户态抢占时机 1、 从系统调用返回用户空间 2、 从中断返回用户空间 内核态抢占时机 1、中断处理程序返回内核空间 可以看到最终是到了 preempt_schedule_irq 2、当内核从non-preemptible&#xff08;禁止抢占&#xff09;状态变成pr…

唤醒金融数据中台:我的数据驱动秘籍

目录 一、明析业务痛点和机会点二、数据驱动精准化营销三、一体化数据平台——整合金融数据1. 数据整合与标准化2. 数据服务与共享3.业务体系集中化 四、强化金融数据安全&#xff0c;筑牢数据保护防线 在当今数字化时代的大潮中&#xff0c;数据无疑是金融行业最耀眼的财富。作…