闭散列哈希表

一、什么是 哈希

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://www.mzph.cn/diannao/10008.shtml

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

相关文章

国产开源物联网操作系统

软件介绍 RT-Thread是一个开源、中立、社区化发展的物联网操作系统&#xff0c;采用C语言编写&#xff0c;具有易移植的特性。该项目提供完整版和Nano版以满足不同设备的资源需求。 功能特点 1.内核层 RT-Thread内核包括多线程调度、信号量、邮箱、消息队列、内存管理、定时器…

JVM基础之垃圾回收

垃圾回收 1. Base 内存泄漏&#xff1a;不再使用的对象在系统中未被回收 内存溢出&#xff1a;内存泄漏的积累 手动触发垃圾回收&#xff1a;System.gc(),该方法不一定会立即回收垃圾&#xff0c;仅仅是向JVM发送一个垃圾回收请求&#xff0c;具体是否需要垃圾回收由JVM自行…

怎么找回回收站里删除的XLS文件?5个恢复方法

我们经常会使用到XLS文件来存储和整理数据。然而有时候由于误操作或不小心&#xff0c;我们可能会将重要的XLS文件删除&#xff0c;并且这些文件可能还被清空出了回收站。面对这种情况许多人会感到焦虑和无助。但是不必过于担心&#xff0c;因为有专门的软件可以帮助我们找回这…

(Java)心得:LeetCode——5.最长回文子串

一、原题 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串。 示例 1&#xff1a; 输入&#xff1a;s "babad" 输出&#xff1a;"bab" 解释&#xff1a;"aba"…

【Linux】什么是进程?

一个正在执行的程序&#xff0c;我们称之为进程。 然后我们来顺着一条线来思考。 操作系统底层是用C语言编写的&#xff0c;而我们的进程&#xff0c;它会有各种属性&#xff0c;那么各种属性就可以用一个结构体来对进程的各个属性进行描述&#xff0c;然后这个结构体里面&…

手写一个SPI FLASH 读写擦除控制器(未完)

文章目录 flash读写数据的特点1. 扇擦除SE&#xff08;Sector Erase&#xff09;1.1 flash_se 模块设计1.1.1 信号连接示意图&#xff1a;1.1.2 SE状态机1.1.3 波形图设计&#xff1a;1.1.4 代码 2. 页写PP(Page Program)2.1 flash_pp模块设计2.1.1 信号连接示意图&#xff1a;…

Soviet Kitchen

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

Codeforces Round 217 (Div. 2) A. Rook, Bishop and King(BFS)

Rook, Bishop and King 题面翻译 【题目描述】 佩蒂亚正在学习国际象棋。他已经学会如何移动王、车和象。让我们提示你如何移动国象棋子。棋盘有 64 64 64个棋格&#xff0c;呈 8 8 8\times8 88正方形。一个格子可以用 ( r , c ) (r,c) (r,c)来表示—— r r r指行&#xff…

Stable Diffusion的技术原理

一、Stable Diffusion的技术原理 Stable Diffusion是一种基于Latent Diffusion Models&#xff08;LDMs&#xff09;实现的文本到图像&#xff08;text-to-image&#xff09;生成模型。其工作原理主要基于扩散过程&#xff0c;通过模拟数据的随机演化行为&#xff0c;实现数据…

回表的原理竟然这么简单

“回表” 是指在使用辅助索引&#xff08;非聚簇索引&#xff09;作为条件进行查询时&#xff0c;由于辅助索引中只存储了索引字段的值和对应的主键&#xff08;聚簇索引&#xff09;键值&#xff0c;因此需要根据主键&#xff08;聚簇索引&#xff09;中的键值去查找实际的数据…

verilog中含有无关项的序列检测

编写一个序列检测模块&#xff0c;检测输入信号a是否满足011XXX110序列&#xff08;长度为9位数据&#xff0c;前三位是011&#xff0c;后三位是110&#xff0c;中间三位不做要求&#xff09;&#xff0c;当信号满足该序列&#xff0c;给出指示信号match。 程序的接口信号图如…

Python 中的 Lambda 函数:简单、快速、高效

大家好&#xff0c;今天再给大家介绍一个python的一个强大工具Lambda 函数&#xff0c;它允许你快速定义简单的匿名函数。这种函数是“匿名的”&#xff0c;因为它们不需要像常规函数那样被明确命名。 在本文中&#xff0c;我们将通过清晰的解释和实用的示例&#xff0c;深入了…

基于GIS地理技术+智慧巡检解决方案(Word原件)

传统的巡检采取人工记录的方式&#xff0c;该工作模式在生产中存在很大弊端&#xff0c;可能造成巡检不到位、操作失误、观察不仔细、历史问题难以追溯等现象&#xff0c;使得巡检数据不准确&#xff0c;设备故障隐患得不到及时发现和处理。因此建立一套完善的巡检管理系统是企…

【2022 深圳 ArchSummit 】大数据架构稳定性保障实践

文章目录 一、前言二、现状三、大数据架构的历史变迁&#xff08;一&#xff09;洪荒期&MR&#xff08;二&#xff09;远古期&MPP&#xff08;四&#xff09;近现代&Flink/Spark&#xff08;五&#xff09;现如今&实时数据湖架构 四、架构稳定的关键因素&#…

编程式导航

目录 一、问题引入 二、基本跳转 1.path路径跳转&#xff08;简易方便&#xff09; 2.name命名路由跳转&#xff08;适合path路径长的场景&#xff09; 三、路由传参 1.path路径跳转传参 &#xff08;1&#xff09;query传参 &#xff08;2&#xff09;动态路由传参 2.…

家装新宠!装修APP开发解决方案,为业主提供全新装修模式

随着人们对家庭装修的需求度越来越高&#xff0c;装修APP开发也随之出现。如今装修APP开发可实现互联网与传统家装行业的信息结合&#xff0c;由传统的家装行业广告模式向移动端的互联网模式进行转移&#xff0c;实现传统家装行业与互联网的相辅相成&#xff0c;以此来推动家装…

电商核心技术揭秘54: 粉丝经济的挖掘与利用

相关系列文章 电商技术揭秘相关系列文章合集&#xff08;1&#xff09; 电商技术揭秘相关系列文章合集&#xff08;2&#xff09; 电商技术揭秘相关系列文章合集&#xff08;3&#xff09; 电商技术揭秘四十一&#xff1a;电商平台的营销系统浅析 电商技术揭秘四十二&#…

一、数据结构的三要素

数据的存储结构&#xff1a;顺序&#xff08;物理位置相邻&#xff09;、链式&#xff08;物理位置不相邻&#xff09;、索引&#xff08;还需要建立索引表&#xff09;、散列&#xff08;根据关键字直接计算出该元素的存储地址又称为hash存储&#xff09;、 时间复杂度&#x…

【SpringSecurity源码】过滤器链加载流程

theme: smartblue highlight: a11y-dark 一、前言及准备 1.1 SpringSecurity过滤器链简单介绍 在Spring Security中&#xff0c;过滤器链&#xff08;Filter Chain&#xff09;是由多个过滤器&#xff08;Filter&#xff09;组成的&#xff0c;这些过滤器按照一定的顺序对进…

AI绘画Stable Diffusion 插件篇:智能标签提示词插件sd-danbooru-tags-upsampler

大家好&#xff0c;我是向阳。 关于智能标签提示词插件&#xff0c;在很早之前就介绍过很多款了&#xff0c;今天再给大家介绍一款智能标签提示词插件sd-danbooru-tags-upsampler。该智能提示词插件是今年2月23号才发布的第一版V0.1.0&#xff0c;算是比较新的智能提示词插件。…