闭散列哈希表

一、什么是 哈希

1.1 哈希概念 与 哈希冲突

在正式介绍闭散列哈希之前,我们需要明确 哈希 的概念。

哈希 :构造一种数据存储结构,通过函数 HashFunc() ,使 元素的存储位置其对应的键值 建立一一映射关系,在此基础上,可以只凭借 O(1) 的时间复杂度查找到目标元素。

举一个过去我们常见的例子:

在统计字符串中各小写字母出现的次数时,我们通常创建 int count[26] = { 0 }; 的这样一个数组,'a' 与 下标为 0 的位置映射,'b' 与 下标为 1 的位置映射,以此类推。

通过以上举例可见,我们对 哈希 其实并不陌生,但是由此衍生出两个问题:

  1. 在数据范围集中时,我们可以通过开一定大小的空间实现下标与元素的一一映射;但如果出现这样一组分散的数据 1, 2, 12, 99, 10000, 6 呢?

  2. 提前把第一个问题的答案告诉各位: 除留余数法 可以解决问题 —— 开一个大小为 10 的数组,每个数据 % 10 后再存进数组中。

    但,如何避免 “哈希冲突” —— 不同的键值计算出相同的哈希地址 呢?比如:2 % 10 == 12 % 10 == 2,如何规避二者冲突的问题?

1.2 哈希函数

引起哈希冲突的原因很可能是:哈希函数设计的不够合理 —— 哈希函数最好能保证所有元素均匀地分布在整个哈希空间中

常见的哈希函数:

  1. 直接定址法。比如:小写字母次数映射。

  2. 除留余数法。

二、闭散列

闭散列:开放定址法 —— 如果发生了 “哈希冲突” 且当前的哈希空间并未被“填满”,此时,把新插入的冲突元素存到 “下一个”空位置 去。

2.1 线性探测

2 % 10 == 12 % 10 == 2 发生了哈希冲突,同时 下标为 2 的下一个位置 —— 下标为 3 为空,就把 12 放在这里;

如果 下标为 3 位置也已经存了元素,就一直往后找 —— 在哈希空间中循环查找,直到找到一个空位置,再把元素插入其中。

通过上面的解释,相信大家已经明了 线性探测 的基本要义,下面再给出它的定义。

线性探测:从发生冲突的位置开始,依次向后寻找,直到找到下一个空位置为止。

2.2 引入状态量

假定我们要将 2 删除,同时插入 32 —— 拷贝一张新的哈希表,再将除了 2 以外的其他数据插入新表,这种做法显然太低效;

如果把 2 以后的每个元素往前移,则改变了元素与哈希地址的映射关系。

因此,我们需要在每个哈希地址增加一个状态量 —— EMPTY(空),EXIST(存在),DELETE(删除),默认构造把所有位置初始化为 EMPTY ,插入元素的同时将 EMPTY 改为 EXIST ,删除元素再将 EXIST 改为 DELETE

通过每个哈希位置的状态量,判断此处是否为空,是否可以插入元素等等。

2.3 闭散列的框架搭建
  • 枚举状态量

    enum State{EMPTY,EXIST,DELETE};
  • HashData

    template<class K, class V>struct HashData{pair<K, V> _kv;State _state = EMPTY; // 默认初始化为空};    
  • HashTable

    template<class K, class V>class HashTable{public:HashTable(size_t n = 10){_tables.resize(n);// resize() 可以保证 size == capacity}private:vector<HashData<K, V>> _tables;size_t _n = 0;// 当前哈希表中的元素个数};
2.4 Insert() 及引入 HashFunc()

解释一个概念 :负载因子 = 哈希表中所存元素的个数 / 表的大小

    // 先给出一个基本的 Insert 函数bool Insert(const pair<K, V>& kv){if (Find(kv.first)) // 未实现的 Find(),找到了返回映射的哈希位置指针,没找到返回空{return false; // 已经存在,插入失败}// 扩容逻辑if ((double)_n / _tables.size() >= 0.7) // 将 负载因子 控制在 0.7{// 创建一个空间为 原表两倍大小 的表HashTable<K, V> newTable(2 * _tables.size()); for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST)newTable.Insert(_tables[i]._kv); }_tables.swap(newTable._tables);}// 插入逻辑size_t hashi = kv.first % _tables.size(); // 计算相应的 哈希地址while (_tables[hashi]._state == EXIST)// 线性探测{hashi++;hashi %= _tables.size();}// 代码运行到这里则必然找到了一个可以插入的位置_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST; // 修改对应状态_n++;return true;}
​void Test_Insert1(){int arr[] = { 1, 4, 24, 34, 7, 44, 17, 20 };HashTable<int, int> ht;for (auto e : arr){ht.Insert(make_pair(e, e));}}

扩容逻辑中复用 Insert() 的部分确实精妙绝伦,newTable 的 size 是原表的 2 倍,因此在插入过程中,不会出现重复扩容进而死循环的状态。

以上的 Insert() 看上去似乎没什么问题,可是,一旦我们把传入两个 string —— HashTable<string, string> ,再 Insert(make_pair<"sort", "排序">) 就出问题了 —— string 类型不支持 size_t hashi = kv.first % _tables.size(); 的方式计算哈希地址!

所以我们需要一个哈希函数 —— HashFunc()(仿函数) ,用于将任意长度的输入数据映射到固定长度输出值(哈希值或散列值)

    template<class K>struct HashFunc{size_t operator()(const K& key){size_t ret = key;return ret;}};
​// 为 string 写一个特化版本template<>struct HashFunc<string>{size_t operator()(const string& s){size_t hash = 0;for (auto& e : s){hash = hash * 131 + e; // 131 是前辈用大量数据测试得到的值,可以尽大程度避免哈希冲突}return hash;}};

有了 HashFunc,我们再对以上的内容做一下改造:

    template<class K, class V, class Hash = HashFunc<K>>class HashTable{public:HashTable(size_t n = 10){_tables.resize(n);}bool Insert(const pair<K, V>& kv){Hash hs;if (Find(kv.first)) {return false;}
​// 扩容逻辑if ((double)_n / _tables.size() >= 0.7) {HashTable<K, V> newTable(2 * _tables.size()); for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST)newTable.Insert(_tables[i]._kv); }_tables.swap(newTable._tables);}
​// 插入逻辑size_t hashi = hs(kv.first) % _tables.size(); // hs(kv.first) 利用哈希函数计算 映射的哈希地址while (_tables[hashi]._state == EXIST){hashi++;hashi %= _tables.size();}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST; _n++;return true;}private:vector<HashData<K, V>> _tables;size_t _n = 0;};

再运行一下:

    void Test_Insert2(){HashTable<string, string> ht;ht.Insert(make_pair("sort", "排序"));ht.Insert(make_pair("iterator", "迭代器"));
​}

就不会出错啦!

Hash 是一个模板接口,当自定义类型不支持仿函数模板推演的时候,你可以传入自己的 HashFunc 完成对应功能!

2.5 Find()Erase()
    HashData<K, V>* Find(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();while (_tables[hashi]._state != EMPTY) {if (_tables[hashi]._kv.first == key && _tables[hashi]._state == EXIST)return &_tables[hashi];hashi++;hashi %= _tables.size();}return nullptr;}

中间过程,有些值可能被删除 —— 状态为 DELETE。

    bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret){ret->_state = DELETE;--_n;return true;}elsereturn false;}

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

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

相关文章

数电——集成计数器(部分)

数电77-集成二进制同步计数器_哔哩哔哩_bilibili 74LS191 同步二进制可逆计数器 单时钟 功能&#xff1a; 要想有置零功能&#xff0c;可以将输入改为0000&#xff0c;然后运用功能里的置数功能 双时钟74LS193 四位同步二进制可逆计数器 功能&#xff1a; 74LS197 二-八-…

nestjs 全栈进阶--中间件

视频教程 22_nest中中间件_哔哩哔哩_bilibili 1. 介绍 在Nest.js框架中&#xff0c;中间件&#xff08;Middleware&#xff09;是一个非常重要的概念&#xff0c;它是HTTP请求和响应生命周期中的一个重要组成部分&#xff0c;允许开发者在请求到达最终的目的控制器方法之前或…

Java苍穹外卖05-订单状态定时处理-数据统计-导出excel

一、订单状态定时处理 1.Spring Task ①介绍 应用场景&#xff1a; ②cron表达式 https://cron.qqe2.com/ ③入门案例 2.需求分析 3.代码开发 每一分钟检查是否存在超时15分钟的订单 每天凌晨一点处理上一条处于派送中的订单 mapper&#xff1a; 二、来单提醒、客户催单 1…

前端崽的java study笔记

文章目录 basic1、sprint boot概述2、sprint boot入门3、yml 配置信息书写和获取 basic 1、sprint boot概述 sprint boot特性&#xff1a; 起步依赖&#xff08;maven坐标&#xff09;&#xff1a;解决配置繁琐的问题&#xff0c;只需要引入sprint boot起步依赖的坐标就行 自动…

IF:23.2|从实验室到田间,微生物干预提高植物抗逆

期刊&#xff1a;Nature Food 影响因子&#xff1a;23.2 发表时间&#xff1a;2023年10月 本研究介绍了一种名为SynCom的微生物组合&#xff0c;该组合Rhodococcus erythropolis和Pseudomonas aeruginosa两种微生物组成。这两种微生物能够帮助水稻抵抗铝毒害和磷缺乏&…

##13 如何在Python中优雅地使用异常处理

文章目录 引言1. 异常处理基础2. 处理多种异常3. 捕捉所有异常4. finally 语句5. 自定义异常结语参考链接 引言 在编程中&#xff0c;错误是在所难免的。Python提供了异常处理机制&#xff0c;允许程序在遇到错误时优雅地恢复。本文将介绍Python中异常处理的基本概念&#xff…

activiti 工作流基本使用

Activiti 介绍 Activiti 是一个开源架构的工作流引擎&#xff0c;基于bpmn2.0 标准进行流程定义。其前身是JBPM&#xff0c;Activiti 通过嵌入到业务系统开发中进行使用。 官方是这样介绍 activiti的&#xff1a; Activiti 是领先的轻量级、以 Java 为中心的开源 BPMN 引擎&…

前端小技巧:如何自定义网页的右键菜单(如何禁用网页的右键菜单)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 右键菜单设置 📒📝 自定义右键菜单实现步骤📝 示例代码📝 涉及的JavaScript语法和参数📝 禁用特定区域的右键菜单⚓️ 相关链接 ⚓️📖 介绍 📖 在网页设计中,一个直观且个性化的右键菜单可以显著提升用户的交互…

【全开源】JAVA上门家政服务系统源码微信小程序+微信公众号+APP+H5

功能介绍 用户端&#xff1a;精准分类、支持家政、维修、万能服务、一口价、报价、线上、各类家政服务、优惠专区、师傅入驻、商家入驻、我的需求、补费明细、我的投诉 师傅端&#xff1a;接单池、消息通知、接单管理、今日订单、师傅入驻、我的钱包、实名认证 商家端&#…

WM Transaction Code 仓库管理模块事务代码大全

1.1 LE-WM 仓库管理 Warehouse Management 仓库管理事务码 描述 LB01 Create Transfer Requirement 创建转储需求 LB02 Change transfer requirement 修改转储需求 LB03 Display Transfer Requirement 显示转储需求 LB10 TRs for Storage Type 按仓储类型的转储请求 …

乡村振兴与农村基础设施建设:加大农村基础设施建设投入,提升农村公共服务水平,改善农民生产生活条件,构建宜居宜业的美丽乡村

一、引言 乡村振兴是我国现代化进程中的重要战略&#xff0c;而农村基础设施建设则是乡村振兴的基石。随着城市化进程的加快&#xff0c;农村基础设施建设滞后的问题日益凸显&#xff0c;成为制约乡村发展的瓶颈。因此&#xff0c;加大农村基础设施建设投入&#xff0c;提升农…

利用自适应深度学习优化OCR文字识别性能

摘要&#xff1a; 随着深度学习技术的不断发展&#xff0c;OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;系统在文档处理、图像搜索和自动化数据提取等领域扮演着重要角色。然而&#xff0c;由于不同场景下文本的多样性和复杂性&#xf…

uni-appH5Android混合开发三 || uni-app调用Android原生方法的三种方式

前言&#xff1a; 关于H5的调用Android原生方法的方式有很多&#xff0c;在该片文章中我主要简单介绍三种与Android原生方法交互的方式。 uni-app跨平台框架介绍和快速入门 uni-app跨平台框架介绍和快速入门 一、H5方法调用android原生方法 H5 Android开发规范官方文档&#…

08.1.自定义图形

自定义图形 创建图形 随便选择几个参数直接添加 选择自定义折线图形查看

大语言模型的后处理

后处理的输入 常规意义上的大模型处理流程 import torch from transformers import LlamaForCausalLM, LlamaTokenizer# 加载模型和tokenizer model LlamaForCausalLM.from_pretrained("decapoda-research/llama-7b-hf") tokenizer LlamaTokenizer.from_pretrain…

c++11 标准模板(STL)本地化库 - 平面类别(std::money_put) - 格式化货币值为字符序列以输出

本地化库 本地环境设施包含字符分类和字符串校对、数值、货币及日期/时间格式化和分析&#xff0c;以及消息取得的国际化支持。本地环境设置控制流 I/O 、正则表达式库和 C 标准库的其他组件的行为。 平面类别 格式化货币值为字符序列以输出 std::money_put template< …

OpenCV使用 Kinect 和其他兼容 OpenNI 的深度传感器(75)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:使用 OpenCV 创建视频(74) 下一篇 :OpenCV使用 Orbbec Astra 3D 相机(76) 目的&#xff1a;​ 通过 VideoCapture 类支持与 OpenNI 兼容的深度传感器&#xff08;Kinect、XtionPRO 等&#xff09;。…

Soviet Kitchen

苏联厨房-具有道具和带有碰撞器的模块化建筑部件的游戏环境资产 内部资产包: 网格-253 前言-98 材料-26 纹理-116 网格格式-(FBX) 纹理格式-(PNG) 资产列表: _BigShelf 多边形计数-1986 文本大小-2048x2048 可以 多边形计数-277 结构尺寸-512x512 _Celling 多边形计数-1 …

STL——queue容器【队列】

queue的基本概念&#xff1a; 概念&#xff1a;queue是一种先进先出的数据结构&#xff0c;它有两个出口 queue的构造函数&#xff1a; 构造函数&#xff1a; queue<T>que:采用模板类实现&#xff0c;queue对象的默认构造形式 queue(const queue &que):拷贝构造函…

番外篇 | YOLOv8改进之利用SCINet解决黑夜目标检测问题 | 低照度图像增强网络

前言:Hello大家好,我是小哥谈。自校正照明网络(Self-Calibrating Illumination Network, SCINet)是一种基于深度学习的图像照明算法,可以自动分析图像的内容并根据图像内容自动优化照明。SCINet是一种专为低光照图像增强设计的框架。它通过级联照明学习过程和权重共享机制…