【C++】哈希表的模拟实现

目录

一、闭散列(开放定址定法)

1、哈希表的结构:

2、哈希表的插入:

3、哈希表的查找:

4、哈希表的删除:

二、开散列(哈希桶)

1、哈希表的结构:

2、构造与析构:

3、哈希表的插入:

4、哈希表的查找:

5、哈希表的删除:

三、拓展:


前言:

在模拟实现哈希表的过程中,每当发生哈希冲突之后有两种方法进行解决:分别是开放定址法链地址法(哈希桶)

一、闭散列(开放定址定法)

闭散列开放定址法是当使用哈希函数计算出的地址发生哈希冲突后,依次向下一个位置去寻找,直到找到空位置,然后填进去。

1、哈希表的结构:

每个位置中存储的数据中需要有两个信息:键值对和这个位置的状态

键值对:可以是K结构或者是K/V结构。

所处位置的状态:

	enum STATE{EXIST,EMPTY,DELETE};

EXIST :存在,表示这里存在数据

EMPTY :空,表示这里是空

DELETE :删除,表示这里的值被删除了,这也是需要状态的原因,因为当对哈希表进行删除的时候,并不是将里面的值进行删除,更好的是将这里面的状态进行修改就可以了。

以下就是哈希表中每个位置所存储的信息

	template<class K, class V>struct HashDate{pair<K, V> _kv;STATE _state = EMPTY;};

哈希表的框架:

_table这个是存放哈希表的数组,

_n是这个哈希表中存在元素的个数

template<class K, class V>
class HashTable
{
public:HashTable(){_table.resize(10);}
private:vector<HashDate<K, V>> _table;size_t _n = 0;
};

接着在public下面实现插入,查找,删除等等。

2、哈希表的插入:

思路:

首先通过哈希函数找到所处位置,接着依次判断是否为存在,如果是存在就向后走,如果找到第一个不存在的将这个位置的_kv修改为kv,并且将这个位置的状态修改为EXIST存在。

接着++_n。

但是在插入之前要进行判断,如果负载因子大于0.7的时候就进行扩容

判断扩容思路

首先看负载因子的大小,当负载因子大于0.7的时候就进行扩容,首先创建一个新的哈希数组,要resize为原来数组的两倍大小,开好后再进行映射过去。

映射思路:

for循环中,每当找到一个位置的状态为EXIST的时候,就将这个数插入到新开好的数组中,因为在插入的时候会判断负载因子所以就不会出现无限循环,这个新的数组就是我需要的,

最后将_table和新创的哈希表进行交换即可

bool Insert(const pair<K, V>& kv)
{//看负载因子的大小if (_n * 10 / _table.size() >= 7){size_t newsize = _table.size() * 2;HashTable<K, V> ht;ht._table.resize(newsize);//开一个新的哈希数组,把这个原来的映射过去for (size_t i = 0; i < _table.size(); i++){if (_table[i]._state == EXIST){ht.Insert(_table[i]._kv);}}_table.swap(ht._table);}//扩容逻辑size_t hashi = kv.first % _table.size();while (_table[hashi]._state == EXIST){++hashi;hashi = hashi % _table.size();//防止hashi大于capacity}_table[hashi]._kv = kv;_table[hashi]._state = EXIST;++_n;return true;
}

注意:

因为存在负载因子的原因,所以哈希表是不可能被装满的。

在扩容的时候,是将原哈希表重新映射到新位置,而不仅仅是拷贝过去,如下图15就可以看出是重新映射而不是拷贝。

3、哈希表的查找:

思路:

首先:通过哈希函数计算出对应的地址。
然后,从哈希地址处开始向后找,直到找到待查找的元素则为查找成功,或找到一个状态为EMPTY的位置判定为查找失败。


注意: 在查找过程中,找到的元素的状态必须是EXIST,并且key值匹配的元素,才算查找成功。若仅仅是key值匹配,但该位置当前状态为DELETE,则还需继续进行查找,因为该位置的元素已经被删除了,如果是EMPTY就证明是在这个哈希表中不存在这个元素,查找失败

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

4、哈希表的删除:

哈希表的删除就比较好搞了,并不是传统意义上的删除,而是直接将所删除的位置的状态修改为DELETE即可。

思路:

首先用Find函数进行查找,没找到删除失败,找到后就将这个位置的状态修改一下,将_n--

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

测试插入,查找,删除:

int main()
{open_address::HashTable<int, int> ht;ht.Insert(make_pair(1, 1));ht.Insert(make_pair(9, 9));ht.Insert(make_pair(15, 15));ht.Insert(make_pair(4, 4));ht.Insert(make_pair(13, 13));ht.Insert(make_pair(2, 2));ht.Insert(make_pair(7, 7));ht.Insert(make_pair(17, 17));ht.Insert(make_pair(6, 6));if (ht.Find(7)){cout << "7在哈希表里面" << endl;}else{cout << "7不在哈希表里面" << endl;}ht.Erase(7);if (ht.Find(7)){cout << "7在哈希表里面" << endl;}else{cout << "7不在哈希表里面" << endl;}return 0;
}

二、开散列(哈希桶)

在每一个位置中,不仅仅是只有数据和状态了,还挂着一个单链表,和指向这个单链表的下一个节点的指针,哈希表的每个位置存储的实际上是某个单链表的头结点,

总体框架大概像下面

(每个单链表的整体看做一个哈希桶)

1、哈希表的结构:

每一个节点的定义

template<class K, class V>
struct HashNode
{pair<K, V> _kv;HashNode<K, V>* _next;HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr){}
};
哈希表这个数组中就变成了节点的指针
template<class K, class V>
class HashTable
{typedef HashNode<K, V> Node;
public:private:vector<Node*> _table;size_t _n = 0;
};

2、构造与析构:

构造函数就是将这个哈希表进行初始化,开好空间,都置为空。

析构函数就是将每一个哈希桶都删除掉,然后将哈希表中的每一个指针都置空

HashTable()
{_table.resize(10, nullptr);
}~HashTable()
{for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}
}

3、哈希表的插入:

思路:

总体来说和闭散列的差不多

首先通过find函数查找这个数,如果有就不能够插入但会false,再通过哈希函数找到待插入的位置为hashi,接着new一个新节点作为待插入节点,将这个节点头插到哈希桶中。

待插入节点的next指针指向了newTable的下标为hashi的这个元素中链表的第一个节点,再把newTable[hashi]的值改成cur的指针,就可以吧cur头插到下标为hashi的这个链表中了

bool Insert(const pair<K, V>& kv)
{if (Find(kv.first)){return false;}if (_table.size() == _n){size_t newSize = _table.size() * 2;vector<Node*> newTable;newTable.resize(newSize, nullptr);for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t hashi = cur->_kv.first / newTable.size();//将cur的_next指向newTable所在位置的第一个节点cur->_next = newTable[hashi];//newTable[hashi]处的指针指向curnewTable[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newTable);}size_t hashi = kv.first % _table.size();Node* newnode = new Node(kv);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return true;
}

4、哈希表的查找:

思路:

首先通过哈希函数找到哈希地址,之后遍历当前位置的哈希桶,找到就返回已查找的节点否则返回空。

Node* Find(const K& key)
{size_t hashi = key % _table.size();Node* cur = _table[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;
}

5、哈希表的删除:

思路:

首先通过哈希函数找到哈希地址,之后遍历当前位置的哈希桶,找到待删除节点的位置后就将待删除节点的上一个位置的next指针指向待删除节点的下一个位置,在delete待删除节点即可。

bool Erase(const K& key)
{size_t hashi = key % _table.size();Node* cur = _table[hashi];Node* prev = nullptr;while (cur){if (cur->_kv.first == key){if (prev == nullptr){_table[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}--_n;return false;
}

测试插入,查找,删除:

int main()
{hash_bucket::HashTable<int, int> ht;ht.Insert(make_pair(1, 1));ht.Insert(make_pair(9, 9));ht.Insert(make_pair(15, 15));ht.Insert(make_pair(4, 4));ht.Insert(make_pair(13, 13));ht.Insert(make_pair(2, 2));ht.Insert(make_pair(7, 7));ht.Insert(make_pair(17, 17));ht.Insert(make_pair(6, 6));ht.Print();ht.Erase(17);ht.Erase(13);cout << "删除17,删除13后" << endl;ht.Print();return 0;
}

三、拓展:

上述的哈希表只能插入或者删除整型的,如果是字符串就搞不好,所以就需要仿函数来进行操作,将每一个键值对中取first的通过仿函数将字符串类转化为整型类。

思路:

首先在仿函数中,将模版进行半特化,如下,如果不是string类就走上面的,这就是走个过场,直接返回key,但如果是string类的那么就通过重载运算符(),将str类转化为整型

template<class K>
struct HashFunctest
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct HashFunctest<string>
{size_t operator()(const string& str){size_t n = 0;for (auto e : str){n *= 131;n += e;}return n;}
};

测试:

int main()
{hash_bucket::HashTable<string, string> ht;ht.Insert(make_pair("apple", "苹果"));ht.Insert(make_pair("pear", "梨子"));ht.Insert(make_pair("banana", "香蕉"));ht.Insert(make_pair("watermelon", "西瓜"));ht.Print();cout << "删除pear后" << endl;ht.Erase("pear");ht.Print();return 0;
}

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

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

相关文章

常用shell指令

这些指令通常在adb shell环境中使用&#xff0c;或者通过其他方式&#xff08;如SSH&#xff09;直接在设备的shell中使用。 文件操作命令 ls&#xff1a;列出目录的内容 ls /sdcard cd&#xff1a;改变目录 cd /sdcard/Download pwd&#xff1a;打印当前工作目录 pwd cat&…

【MySQL】提高篇—索引与性能优化:如何创建与管理索引

为了提高查询效率&#xff0c;数据库提供了索引的功能。索引可以看作是数据库表的“目录”&#xff0c;它允许数据库管理系统快速定位到所需的数据行&#xff0c;而无需扫描整个表。 通过合理地创建和管理索引&#xff0c;可以显著提升数据检索的速度和效率。 在实际应用中&a…

炒股中如何克服贪婪与恐惧?

炒股自动化&#xff1a;申请官方API接口&#xff0c;散户也可以 python炒股自动化&#xff08;0&#xff09;&#xff0c;申请券商API接口 python炒股自动化&#xff08;1&#xff09;&#xff0c;量化交易接口区别 Python炒股自动化&#xff08;2&#xff09;&#xff1a;获取…

自动化抖音点赞取消脚本批量处理

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

算法复习核心题目策略总结,以便回顾

以下是自己8年技术面试以来总结的算法考点&#xff01; 1. 基础(要消化的) 基础查找 二叉树 链表 排序&#xff0c;见【基础数据结构思路&写法记录&#xff0c;便于回顾-CSDN博客】 2. 典型常考类型及题目(需要面试前回顾下) 题目列举 二分 链表与数组 二叉树与分治 二叉树…

centos7 nginx优化

优化nginx进程个数的策略 在高并发、高访问量的web服务场景&#xff0c;需要事先启动好更多的nginx进程&#xff0c;以保证快速响应并处理大量并发用户的请求。worker_processes 1;一般调整到与CPU的颗数相同查看LInux可查看CPU个数及总核数grep processor /proc/cpuinfo|wc …

手机摄影入门

感觉会摄影的人是能够从生活中发现美的人。 我不太会拍照&#xff0c;觉得拍好的照片比较浪费时间&#xff0c;而且缺乏审美也缺乏技巧&#xff0c;所以拍照的时候总是拍不好。但有时候还是需要拍一些好看的照片的。 心态和审美可能需要比较长时间提升&#xff0c;但一些基础…

在不支持AVX的linux上使用PaddleOCR

背景 公司的虚拟机CPU居然不支持avx, 默认的paddlepaddle的cpu版本又需要有支持avx才行,还想用PaddleOCR有啥办法呢? 是否支持avx lscpu | grep avx 支持avx的话,会显示相关信息 如果不支持的话,python运行时导入paddle会报错 怎么办呢 方案一 找公司it,看看虚拟机为什么…

重学SpringBoot3-Spring WebFlux之HttpHandler和HttpServer

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-Spring WebFlux之HttpHandler和HttpServer 1. 什么是响应式编程&#xff1f;2. Project Reactor 概述3. HttpHandler概述3.1 HttpHandler是什么3.2 Http…

Rust编写硬件抽象层(HAL)服务

基于Rust编写硬件抽象层&#xff08;HAL&#xff09;服务是一个复杂但有趣的任务&#xff0c;它涉及到嵌入式系统开发的多个方面。以下是一个详细的指南&#xff0c;帮助你理解如何使用Rust编写HAL服务。 一、引言 硬件抽象层&#xff08;HAL&#xff09;是嵌入式系统开发中的…

SQL 中查找重复数据的四种方法

数据库中的重复数据可能导致存储成本增加、查询性能下降、分析结果不准确以及数据管理混乱。本文概述了四种 SQL 技术来检测和处理这些重复数据&#xff1a;使用GROUP BY和HAVING识别重复行的分组&#xff0c;采用诸如ROW_NUMBER()的窗口函数进行高效分析&#xff0c;利用EXIST…

常见的Java面试题

1.在项目中遇到难点是如何处理 数据库性能问题&#xff1a;在数据库处理数据操作&#xff08;如查询、插入、更新、删除等&#xff09;时表现出的响应速度慢、资源利用率高、吞吐量低等不良情况&#xff0c;影响了系统的整体性能和用户体验 解决方案有以下这些&#xff1a; 优化…

有什么牌子的学生台灯性价比高?五款性价比高的学生用台灯

最近不少朋友都在问我&#xff0c;有什么牌子的学生台灯性价比高&#xff1f;说实话&#xff0c;这还真不是个容易回答的问题。市面上的台灯品种琳琅满目&#xff0c;价格从几十到上千都有&#xff0c;功能也是五花八门。选择一款适合自己的护眼台灯&#xff0c;确实需要好好琢…

深度学习中的迁移学习:优化训练流程与提高模型性能的策略,预训练模型、微调 (Fine-tuning)、特征提取

1024程序员节 | 征文 深度学习中的迁移学习&#xff1a;优化训练流程与提高模型性能的策略 目录 &#x1f3d7;️ 预训练模型&#xff1a;减少训练时间并提高准确性&#x1f504; 微调 (Fine-tuning)&#xff1a;适应新任务的有效方法&#x1f9e9; 特征提取&#xff1a;快速…

Flink 1.18安装 及配置 postgres12 同步到mysql5.7(Flink sql 方式)

文章目录 1、参考2、flink 常见部署模式组合3、Standalone 安装3.1 单节点安装3.2 问题13.3 修改ui 端口3.4 使用ip访问 4 flink sql postgres --->mysql4.1 配置postgres 124.2 新建用户并赋权4.3. 发布表4.4 Flink sql4.5 Could not find any factory for identifier post…

深度学习到底是怎么实现训练模型的(以医学图像分割为例

本文主要讲解的主要不是深度学习训练模型过程中的数学步骤&#xff0c;不是讲&#xff1a; 输入——前向传播——反向传播——输出&#xff0c;特征提取&#xff0c;特征融合等等过程。而是对于小白或者门外汉来说&#xff0c;知道模型怎么处理的&#xff0c;在用些什么东西&am…

分享二维码弹窗,实现扫码跳转

1.二维码组件 QRCode CustomDialog export struct ShareDialog {Prop item: QuestionDetail as QuestionDetailcontroller: CustomDialogControllerQRCode(this.item.id).width(160).height(160) } 2.扫码跳转 前置知识&#xff1a; Scan Kit 提供默认界面扫码能力。canIUs…

推荐几个好用的配色网站

1.ColorSpace 地址&#xff1a;ColorSpace - Color Palettes Generator and Color Gradient Tool Color Space 是款功能强大的渐变色在线生成器&#xff0c;支持单色、双色&#xff0c;甚至三色渐变。 进入首页&#xff0c;输入一个颜色&#xff0c;点击 GENERATE&#xff08…

从一个简单的计算问题,看国内几个大语言模型推理逻辑能力

引言 首先&#xff0c;来看问题&#xff1a; 123456*987654等于多少&#xff0c;给出你计算的过程。 从openai推出chatgpt以来&#xff0c;大模型发展的很快&#xff0c;笔者也经常使用免费的大语言模型辅助进行文档编写和编码工作。大模型推出时间也好久了&#xff0c;笔者想…

autMan框架的定时推送功能学习

一、定时推送功能简介 “定时推送”位于“系统管理”目录 主要有两个使用方向&#xff1a; 一是定时向某人或某群发送信息。 二是定时运行某指令&#xff0c;就是机器人给自己发指令&#xff0c;让自己运行此指令。 二、定时推送设置 定时&#xff1a;cron表达式&#xff0c;…