【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&…

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

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

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…

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

最近不少朋友都在问我&#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.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;…

Java 21新特性概述

Java 21于2023年9月19日发布&#xff0c;这是一个LTS&#xff08;长期支持&#xff09;版本&#xff0c;到此为止&#xff0c;目前有Java 8、Java 11、Java 17和Java 21这四个LTS版本。 Java 21此次推出了15个新特性&#xff0c;本节就介绍其中重要的几个特性&#xff1a; JEP…

Ubuntu20.04安装ROS2教程

Ubuntu20.04安装ROS2教程 ROS 2 安装指南支持的ROS 2 版本设置语言环境&#xff08;Set locale&#xff09;设置源&#xff08;Setup Sources&#xff09;设置密钥安装 ROS 2 包&#xff08;Install ROS 2 packages&#xff09;环境设置&#xff08;Environment setup&#xff…

java--反射(reflection)

一、反射机制 Java Reflection &#xff08;1&#xff09;反射机制允许程序在执行期借助 Reflection API 取得任何类的内部信息&#xff08;比如成员变量、构造器、成员方法等等&#xff09;&#xff0c;并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。&#x…

时间序列预测(九)——门控循环单元网络(GRU)

目录 一、GRU结构 二、GRU核心思想 1、更新门&#xff08;Update Gate&#xff09;&#xff1a;决定了当前时刻隐藏状态中旧状态和新候选状态的混合比例。 2、重置门&#xff08;Reset Gate&#xff09;&#xff1a;用于控制前一时刻隐藏状态对当前候选隐藏状态的影响程度。…

Java项目-基于springboot框架的智慧外贸系统项目实战(附源码+文档)

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

小新学习K8s第一天之K8s基础概念

目录 一、Kubernetes&#xff08;K8s&#xff09;概述 1.1、什么是K8s 1.2、K8s的作用 1.3、K8s的功能 二、K8s的特性 2.1、弹性伸缩 2.2、自我修复 2.3、服务发现和负载均衡 2.4、自动发布&#xff08;默认滚动发布模式&#xff09;和回滚 2.5、集中化配置管理和密钥…

高效改进!防止DataX从HDFS导入关系型数据库丢数据

高效改进&#xff01;防止DataX从HDFS导入关系型数据库丢数据 针对DataX在从HDFS导入数据到关系型数据库过程中的数据丢失问题&#xff0c;优化了分片处理代码。改动包括将之前单一分片处理逻辑重构为循环处理所有分片&#xff0c;确保了每个分片数据都得到全面读取和传输&…