【C++进阶学习】第九弹——哈希的原理与实现——开放寻址法的讲解

前言:

在前面,我们已经学习了很多存储机构,包括线性存储、树性存储等,并学习了多种拓展结构,效率也越来越高,但是是否有一种存储结构可以在大部分问题中都一次找到目标值呢?哈希可能能实现

目录

一、哈希的概念

二、哈希冲突

三、哈希冲突解决

3.1 开放寻址法

节点结构

插入操作

查找操作

删除操作

打印操作

3.2 链地址法

四、测试代码(开放寻址法)

五、总结


一、哈希的概念

哈希就是一种特殊的存储结构,通过特定的函数,使得数据的存储位置与它的关键码之间建立一种一一映射的关系,这样在查找数据时就可以直接通过关键值来快速查找

通过这种方法:

当我们向其中插入数据时,就可以利用此特定函数插入到关键码对应的位置下

当我们搜索数据时,通过关键码,进行相应的处理就可以找到要找的数据

这种方法就叫做哈希,特定的函数就是哈希函数,这种方法所建立的结构就叫做哈希函表

我们来看这样一个例子:

对于这样一个数组{1,5,3,17,19,0},按照上述规则我们首先要先找一个合适的哈希函数,

这里我们哈希函数可以设为:hashi(key)=key%capacity;capacity为存储空间底层空间总的大小

现在我们根据上面这个例子来思考这样一个问题,如果有这样一个数据,比如13,通过上面的哈希函数计算得我们应该把它放在关键码为3的位置上,但是此时这个位置上已经有数据了,我们应该如何解决呢?这样的问题就叫做哈希冲突

二、哈希冲突

哈希冲突指的是在使用哈希表进行数据存储和查找时,不同的关键字通过哈希函数计算得到了相同的哈希值。

哈希函数是将关键字映射到哈希表中的某个位置的函数。由于哈希表的存储空间是有限的,而可能的关键字数量是无限的,所以不同的关键字有可能被映射到相同的位置,这就产生了哈希冲突。

哈希冲突会影响哈希表的性能,比如增加查找、插入和删除操作的时间复杂度。

常见的解决哈希冲突的方法有(这两种方法会在后面详细讲解):

  1. 开放寻址法:当发生冲突时,通过一定的探查方式在哈希表中寻找其他空闲的位置来存储冲突的元素。
  2. 链地址法:在哈希表的每个位置上建立一个链表,将所有哈希值相同的元素都存储在这个链表中。

三、哈希冲突解决

解决哈希冲突常见的两种方法主要是:开放寻址法和链地址法

3.1 开放寻址法

开放定址法是解决哈希冲突的一种方法,其基本思想是当发生冲突时,通过某种系统的方法在哈希表中寻找下一个空槽位,并将冲突的关键码存储在这个槽位中。下面我们先来看一下开放寻址法的重点:

  1. 探测序列:开放定址法中,探测序列决定了当发生冲突时如何查找下一个槽位。常见的探测序列方法有:

    • 线性探测:当冲突发生时,顺序检查表中的下一个槽位,直到找到空槽位。
    • 二次探测:探测序列为 1, 4, 9, 16, ...,即探测位置是 i^2 的倍数,其中 i 是从0开始的整数。
    • 伪随机探测:使用伪随机数生成器来确定探测序列。
  2. 删除操作:在开放定址法中,删除元素比较复杂,因为不能简单地将槽位置为空,否则会影响后续的查找操作。通常,删除一个元素时,将其标记为已删除,但在查找时跳过已删除的元素。

  3. 装填因子:装填因子是哈希表中已存储元素个数与哈希表大小的比值。开放定址法中,装填因子不宜过高,否则冲突概率增加,查找效率下降。(因为这个原因,所以需要扩容)

节点结构

因为我们并不知道插入要操作何种类型的数据,可能是整形,浮点型或string的,所以我们可以选择将它们全转化为整形来处理,这里就需要我们借助仿函数和模板特化来实现

	template<class K>       
struct HashFunc         //仿函数,这里的功能是将其他类型转化为整形
{size_t operator()(const K& key){return (size_t)key;}
};
template<>     //特化
struct HashFunc<string>    //string类的不可以直接转化为整形,所以需要特殊处理
{size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash *= 31;hash += e;}return hash;}
};enum Status{EMPTY,     //此位置为空EXIST,     //此位置不为空DELETE     //此位置数据已被删除};template<class K, class V>     //因为不能确定我们要处理什么类型的数据,所以我们采用类模板的形式struct HashData{pair<K, V> _kv;   Status _s;        //此位置的状态};template<class K, class V, class Hash = HashFunc<K>>class HashTable{public:HashTable()     //初始化HashTable{_tables.resize(10);}private:vector<HashData<K, V>> _tables;size_t _n = 0;        //存储个数};

插入操作

开放寻址法的关键就在于数据的插入,在这里我们重点讲解一下线性探测的思想

这就是线性探测的思路,同时我们还要在装填因子足够大的时候进行扩容,比如上面这个例子,此时10个位置中已经填入7个因子,我们就可以进行按2倍扩容:

代码实现如下:

		//插入bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;//负载因子0.7就扩容if (_n * 10 / _tables.size() == 7){size_t newSize = _tables.size() * 2;HashTable<K, V, Hash> newHT;newHT._tables.resize(newSize);for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._s == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);    //直接交换就可以得到扩容后的结果}Hash hf;//线性探测size_t hashi = hf(kv.first) % _tables.size();  //这里涉及到模板的特化,后面会讲while (_tables[hashi]._s == EXIST)    //当此位置不为空时,就往后找{                                     //当为空或已删除时都是可以放入数据的hashi++;hashi %= _tables.size();        //这里进行这个操作的目的是防止hashi大于_tables.size()}_tables[hashi]._kv = kv;     //找到后将这个位置值改为插入值_tables[hashi]._s = EXIST;   //状态改为存在_n++;return true;}

查找操作

上面的插入操作中,我们首先就先用查找操作看是否已经有这个数据,因为哈希是不允许存在重复数据的,这里我们就来看一下这个查找操作

		//查找HashData<K, V>* Find(const K& key){Hash hf;      //仿函数size_t hashi = hf(key) % _tables.size();   //所有计算都要用仿函数将key转换为整形while (_tables[hashi]._s == EXIST)      //从不为空的位置开始找{if (_tables[hashi]._s != EMPTY&& _tables[hashi]._kv.first == key)return &_tables[hashi];hashi++;hashi %= _tables.size();}return NULL;}

删除操作

我们删除一个数据时,并不能简单的找到这个数据就进行删除,这样会对我们后序很多操作带来不少麻烦,比如我们把4删除了,再找44就会很不容易,这也就是我们前面在定义节点结构是要定义状态的原因,我们删除一个操作后可以把它的状态更改,这样我们在进行其他操作的时候就可以直到这个位置上的值已被删除

		//删除bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret){ret->_s = DELETE;_n--;return 0;}elsereturn true;}

打印操作

		//打印void Print(){for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._s == EXIST)     //当这个位置有数据时,打印出有效数据cout << "[" << i << "]->" << _tables[i]._kv.first << ":"<< _tables[i]._kv.second << endl;else if (_tables[i]._s == EMPTY)printf("[%d]->EMPTY\n", i);elseprintf("[%d]->DELETE\n", i);}}

3.2 链地址法

链地址法有些东西与上面的代码有些冲突,不好测试,我们放在下一篇讲

四、测试代码(开放寻址法)

我们给出几个测试用例检验一下上面的开放寻址法是否有误:

测试一:

		void TestHT1(){HashTable<int, int> j;int a[] = { 4,14,24,34,5,7,1 };for (auto e : a){j.Insert(make_pair(e, e));}j.Insert(make_pair(3, 3));j.Print();j.Insert(make_pair(3, 3));     //这里有一个隐藏的bugif (j.Find(3))cout << "3存在" << endl;}

运行结果:

测试二:

	void TestHT2()    //测试string{string arr[] = { "香蕉","甜瓜","苹果","香蕉","苹果","苹果" };HashTable<string, int> ht;for (auto e : arr){auto ret = ht.Find(e);if (ret)ret->_kv.second++;else{ht.Insert(make_pair(e, 1));}}ht.Print();}

运行结果:

五、总结

以上就是用开放寻址法来创建一个哈希表的完整代码,由于排版原因,整体看起来可能有些乱,有不懂的地方欢迎与我私信交流!!

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

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

相关文章

【C++】C++应用案例-翻转数组

翻转数组&#xff0c;就是要把数组中元素的顺序全部反过来。比如一个数组{1,2,3,4,5,6,7,8}&#xff0c;翻转之后就是{8,7,6,5,4,3,2,1}。 &#xff08;1&#xff09;另外创建数组&#xff0c;反向填入元素 数组是将元素按照顺序依次存放的&#xff0c;长度固定。所以如果想要…

C++ | Leetcode C++题解之第283题移动零

题目&#xff1a; 题解&#xff1a; class Solution { public:void moveZeroes(vector<int>& nums) {int n nums.size(), left 0, right 0;while (right < n) {if (nums[right]) {swap(nums[left], nums[right]);left;}right;}} };

Go语言编程 学习笔记整理 第2章 顺序编程 前半部分

前言&#xff1a;《Go语言编程》编著 许式伟 吕桂华 等 1.1 变量 var v1 int var v2 string var v3 [10]int // 数组 var v4 []int // 数组切片 var v5 struct { f int } var v6 *int // 指针 var v7 map[string]int // map&#xff0c;key为string类型&#xff0c;value为in…

shell脚本(fifteen day)

一、shell概述 1、shell概念 &#xff08;1&#xff09;shell 英文翻译过来是外壳的意思&#xff0c;作为计算机语言来理解可以认为它是操作系统的外壳。可以通过shell 命令来操作和控制操作系统&#xff0c;比如Linux中的shell命令就包括 ls、cd、pwd 等等。 &#xff08;2&a…

postgres启动错误

说明&#xff1a;记录一次在Linux上启动postgres数据错误&#xff1b; 问题&#xff1a;安装好postgres数据库后&#xff0c;我使用systemctl启动数据库&#xff0c;报下面的错误 ● postgresql-15.service - PostgreSQL 15 database serverLoaded: loaded (/usr/lib/systemd…

【研路导航】保研英语面试高分攻略,助你一路过关斩将

面试攻略之 千锤百炼英语口语 写在前面 在保研面试中&#xff0c;英语口语往往是让许多同学感到头疼的一部分。如何在面试中展现出自信和流利的英语表达能力&#xff0c;是我们今天要探讨的主题。以下是一些有效的英语口语练习方法和常见题型解析&#xff0c;帮助你在保研面试…

使用PyTorch导出JIT模型:C++ API与libtorch实战

PyTorch导出JIT模型并用C API libtorch调用 本文将介绍如何将一个 PyTorch 模型导出为 JIT 模型并用 PyTorch 的 CAPI libtorch运行这个模型。 Step1&#xff1a;导出模型 首先我们进行第一步&#xff0c;用 Python API 来导出模型&#xff0c;由于本文的重点是在后面的部署…

Java-----栈

目录 1.栈&#xff08;Stack&#xff09; 1.1概念 1.2栈的使用 1.3栈的模拟实现 1.4栈的应用场景 1.5栈、虚拟机栈、栈帧有什么区别呢 1.栈&#xff08;Stack&#xff09; 1.1概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操…

Java基础入门14:常用API(Object(s)类、包装类、Math、Arrays、日期时间、Lambda表达式、方法引用)

Object类 Object类是Java中所有类的祖宗类&#xff0c;因此&#xff0c;Java中所有类的对象都可以直接使用Object类中提供的一些方法。 Object类的常见方法&#xff1a; package com.itchinajie.d12_api_object;public class Test {public static void main(String[] args) {…

mybatis-plus默认字段填充以及批量数据插入优化

日常开发中&#xff0c;我们需要设置一些数据库的默认字段填充&#xff0c;比如创建时间、创建人、更新时间、更新人等等&#xff0c;那么mybatis-plus给我们提供了一个这样的接口去做这件事情 MetaObjectHandler。 1、首先可以创建一个实现类来实现MetaObjectHandler接口 C…

DBMotion x Chat2DB:高效迁移,优雅同步,数据腾飞不再愁

DBMotion 基本介绍 数据传输服务DBMotion是一款轻量、绿色的数据库迁移、同步、校验工具。支持国产化数据迁移、支持容灾演练、支持两地三中心和异地多活&#xff1b;源库无感知、简单易集成、丝滑高性能。助您在多云之间随心迁移、自由容灾。 功能介绍 已支持的数据库 v1.…

7月22日JavaSE学习笔记

Collection接口&#xff0c;还有一个父级接口Iterable可迭代的 Collection继承树 Set 集合 Set的底层是用Map实现&#xff08;存储在key中&#xff0c;value中是空的Object对象&#xff09; 有序&#xff1a;取出的顺序和添加的顺序是一样的。 List是有序的&#xff0c;Set是…

“软件质量”,构筑企业值得信赖的护城河

引子 质量是产品的生命线&#xff0c;质量问题不仅会导致企业财产损失&#xff0c;还可能引发业务中断、客户满意度下降、企业品牌声誉受损等负面影响。如何在软件开发过程中全方位构建产品质量防护盾&#xff0c;是各行业保障产品高质量的重要课题。 如何保障软件质量&#…

五、SpringIoC/DI的使用

1. 类注解、方法注解 告诉spring管理bean—>bean的存储 1、类注解&#xff1a;五大注解 Controller&#xff08;控制器存储&#xff09;、 Service&#xff08;服务存储&#xff09;、 Repository&#xff08;仓库存储&#xff09;、 Component&#xff08;组件存储&#xf…

【Linux】管道通信和 system V 通信

文章目录 一、进程通信原理&#xff08;让不同进程看到同一份资源&#xff09;二、管道通信2.1 管道原理及其特点2.1 匿名管道和命名管道 三、共享内存通信3.1 共享内存原理3.2 创建和关联共享内存3.3 去关联、ipc 指令和删除共享内存 四、消息队列和信号量&#xff08;了解&am…

VirtualSurveyor9.0.3 无人机测绘软件功能介绍

Virtual Surveyor9.0.3中文版是功能强大的无人机测绘软件&#xff0c;使用旨在为用户提供完整的地理空间数据可视化和分析功能&#xff0c;带来提高的生产力&#xff0c;功能全面而强大&#xff0c;在无人机到CAD模型的过程中&#xff0c;使用Virtual Surveyor软件来拆卸输送机…

情绪稳定的人有什么特点?

第一部分&#xff1a;至纯之人&#xff0c;大器晚成 1.1 单纯&#xff0c;不是天真 你知道吗&#xff1f;那些能够成就大事的人&#xff0c;往往在人性上非常单纯。他们对外界的需求很低&#xff0c;更多的是向内寻求。这样的人&#xff0c;他们的内心世界像一片净土&#xff…

数据结构与算法--顺序表(Java)

&#x1f4dd;个人主页&#x1f339;&#xff1a;誓则盟约 ⏩收录专栏⏪&#xff1a;Java SE &#x1f921;往期回顾&#x1f921;&#xff1a;Java SE--基本数据类型&#xff08;详细讲解&#xff09; &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 什么…

每日任务:TCP/IP模型和OSI模型的区别

介绍一下TCP/IP模型和OSI模型的区别&#xff1f; OSI模型由国标准化组织提出&#xff0c;而TCP/IP模型是由美国国防部开发的&#xff1b; OSI模型由七个层次组成&#xff0c;从下到上依次为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。而TCP/IP模型只有四层…

AI视频生成(即梦)

1.打开即梦网页版 https://jimeng.jianying.com/ai-tool/home 2.图片生成-导入参考图&#xff08;这里原本的红色或者灰度图都是可以的&#xff09;-精细度5&#xff08;最高图质量越高&#xff09; 注&#xff1a;根据需要&#xff0c;选择不同的生图模型&#xff0c;具有…