【C++】模拟实现hash_table(哈希表)

🦄个人主页:修修修也

🎏所属专栏:实战项目集

⚙️操作环境:Visual Studio 2022


目录

一.了解项目功能

二.逐步实现项目功能模块及其逻辑详解

📌实现HashNode类模板

🎏构造HashNode类成员变量

🎏实现HashNode类构造函数

📌实现HashTable类模板

🎏构造HashTable类成员变量

🎏实现HashTable类构造函数

🎏实现HashTable类插入函数

🎏实现HashTable类查找函数

🎏实现HashTable类删除函数

🎏实现HashTable类析构函数

三.项目完整代码

test.cpp文件

HashTable.h文件

结语


一.了解项目功能

在本次项目中我们的目标是使用开散列的拉链法解决哈希冲突来实现一个哈希表模板,还不了解哈希表概念的朋友可以先移步[【数据结构】什么是哈希表(散列表)?],其结构图示如下:

        哈希结点(HashNode)需要包含两个成员:键值对_kv,后继结点指针域_next。逻辑结构图示如下:

           哈希表类模板提供的功能有:

  1. 哈希表结点类的构造函数
  2. 哈希表构造函数
  3. 哈希表的析构函数
  4. 哈希表的插入函数
  5. 哈希表的查找函数
  6. 哈希表的删除函数

二.逐步实现项目功能模块及其逻辑详解

通过第一部分对项目功能的介绍,我们已经对哈希表的功能有了大致的了解,虽然看似需要实现的功能很多,貌似一时间不知该如何下手,但我们可以分步分模块来分析这个项目的流程,最后再将各部分进行整合,所以大家不用担心,跟着我一步一步分析吧!


!!!注意,该部分的代码只是为了详细介绍某一部分的项目实现逻辑,故可能会删减一些与该部分不相关的代码以便大家理解,需要查看或拷贝完整详细代码的朋友可以移步本文第四部分。


📌实现HashNode类模板

🎏构造HashNode类成员变量

        我们在一开始需求分析时就已经明确了哈希结点(HashNode)需要包含两个成员:键值对_kv,后继结点指针域_next.结点(RBTreeNode)逻辑结构图示如下:

        综上所述,该部分代码如下:

template<class K, class V>
struct HashNode
{pair<K, V> _kv;HashNode<K, V>* _next;
};

🎏实现HashNode类构造函数

        HashNode的构造函数我们实现两个即可,一个是有参构造,一个是无参构造,而无参构造又可以通过给缺省值的方式和有参构造合二为一,所以我们用初始化列表来实现一下HashNode的构造函数:

HashNode(const pair<K,V>& kv = pair<K,V>()):_kv(kv),_next(nullptr)
{}

📌实现HashTable类模板

🎏构造HashTable类成员变量

        HashTable类成员变量比较简单,底层的链表数组用vector来实现就行, 为了简化模板中出现的HashNode<K,V>类型的名字,我们将其简化命名为:Node。然后再设置一个变量_n来记录当前哈希表中有效元素个数, 方便我们后续扩容使用.

        该部分代码如下:

template<class K, class V, class HashFunc = DefaultHashFanc<K>>//最后一个参数是哈希函数模板
class HashTable
{typedef HashNode<K, V> Node;
public:private:vector<Node*> _table;	//指针数组size_t _n;     //有效元素个数
};

🎏实现HashTable类构造函数

        HashTable类的构造函数非常简单,因为只有两个成员变量_table和_n。对于_table,最开始我们可以调用vector自带的接口来先初始化10个空间的大小方便使用,对于_n最开始肯定是置为0,综上,代码如下:

HashTable()
{_table.resize(10, nullptr);_n = 0;
}

🎏实现HashTable类插入函数

        哈希表的插入逻辑比红黑树简单不少,简单来讲就是先使用哈希函数计算插入位置,然后在表里找对应位置的链表将新结点头插即可。但是在插入之前还有一些小细节,比如要先判断结点在不在哈希表中,如果在就不用插入了。还要判断哈希表的负载因子是否到达1,即哈希表中有效结点个数/哈希表的大小是否=1,如果等于1就需要进行哈希表扩容, 具体的扩容逻辑见代码注释。

        综上,代码如下:

bool Insert(const pair<K, V>& kv)
{//检查结点是否在哈希表中,如果在就返回插入失败if (Find(kv.first)){return false;}HashFunc hf;//扩容逻辑:负载因子到1就扩容if (_n == _table.size()){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 = hf(cur->_kv.first) % newSize;cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}}_table.swap(newTable);}//用哈希函数计算插入位置 size_t hashi = hf(kv.first) % _table.size();//链表头插Node* newnode = new Node(kv);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return true;
}

🎏实现HashTable类查找函数

        开链法哈希表的查找逻辑很简单,就是按照哈希函数去算key的位置,然后将该位置的链表向后遍历查找该元素即可,代码如下:

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

🎏实现HashTable类删除函数

        开链法哈希表的删除逻辑也很简单,就是按照哈希函数去算key的位置,然后将该位置的链表向后遍历查找该元素,找到之后按照链表结点的删除逻辑删除该结点即可,代码如下:

bool Erase(const K& key)
{HashFunc hf;//计算位置size_t hashi = hf(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];//查找并删除链表中的待删结点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;}return false;
}

🎏实现HashTable类析构函数

         哈希表的析构函数我们必须自己实现, 因为无论是vector的析构函数还是默认生成的都不能做到有效释放vector链表中的一个一个结点, 会导致内存泄漏, 所以我们需要自己手动实现.实现逻辑也不难, 逐一遍历哈希表然后逐一释放所有表中结点元素即可, 代码如下:

~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;}
}

三.项目完整代码

我们将程序运行的代码分别在两个工程文件中编辑,完整代码如下:

test.cpp文件

        该文件主要包含哈希表功能测试代码,可酌情参考.

#include"HashTable.h"void test_openadd()
{open_address::HashTable<int, int> ht;int a[] = { 1,111,4,7,15,25,44,9 };for (auto e : a){ht.Insert(make_pair(e, e));}auto ret = ht.Find(4);//ret->_kv.first = 40;ret->_kv.second = 400;//字符串做key. 利用仿函数,类模板的特化    相关算法BKDR Hashopen_address::HashTable<string, string> dict;dict.Insert(make_pair("sort", "排序"));dict.Insert(make_pair("left", "xxx"));dict.Insert(make_pair("insert", "插入"));auto dret = dict.Find("left");dret->_kv.second = "左边";
}int main()
{//test_openadd();hash_bucket::HashTable<int, int> ht;int a[] = { 1,111,4,7,15,25,44,9,14,27,24 };for (auto e : a){ht.Insert(make_pair(e, e));}ht.Print();//字符串做key. 利用仿函数,类模板的特化    相关算法BKDR Hashhash_bucket::HashTable<string, string> dict;dict.Insert(make_pair("sort", "排序"));dict.Insert(make_pair("left", "xxx"));dict.Insert(make_pair("insert", "插入"));dict.Insert(make_pair("string", "字符串"));dict.Insert(make_pair("erase", "删除"));dict.Insert(make_pair("find", "查找"));auto dret = dict.Find("left");dret->_kv.second = "左边";dict.Print();return 0;
}

HashTable.h文件

        该文件中还实现了必散列的线性探测法实现哈希表,和文中主要讲的开链法分别实现在两个命名空间中,供大家参考.

#pragma once
#include<iostream>
#include<vector>
#include<string>
using namespace std;template<class K>
struct DefaultHashFanc
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct DefaultHashFanc<string>
{size_t operator()(const string& str){size_t hash = 0;for (auto e : str){hash *= 131;hash += e;}return hash;}
};//必散列的线性探测法实现哈希表
namespace open_address
{enum STATE{EXIST,	//存在EMPTY,	//空DELETE	//删除};template<class K, class V>struct HashData{pair<K, V> _kv;STATE _state = EMPTY;};template<class K,class V,class HashFunc = DefaultHashFanc<K>>class HashTable{public:HashTable(){_table.resize(10);}bool Insert(const pair<K, V>& kv){if (Find(kv.first)){return false;}//扩容if ((double)_n / _table.size() >= 0.7){size_t newSize = _table.size() * 2;//不能简单的只括容量,还要重新映射HashTable<K, V, HashFunc> newHT;newHT._table.resize(newSize);//遍历旧表的数据插入到新表for (size_t i = 0; i < _table.size(); i++){if (_table[i]._state == EXIST){newHT.Insert(_table[i]._kv);}}_table.swap(newHT._table);}HashFunc hf;//算插入位置size_t hashi = hf(kv.first) % _table.size();//线性探测找插入位置while (_table[hashi]._state == EXIST){++hashi;hashi %= _table.size();	//如果找到表尾,回到表头继续找}//插入数据_table[hashi]._kv = kv;_table[hashi]._state = EXIST;++_n;return true;}HashData<const K, V>* Find(const K& key){HashFunc hf;//线性探测size_t hashi = hf(key) % _table.size();while (_table[hashi]._state != EMPTY){if (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key){return (HashData<const K, V>*) & _table[hashi];}++hashi;hashi %= _table.size();	//如果找到表尾,回到表头继续找}return nullptr;}bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret){ret->_state = DELETE;--_n;return true;}return false;}private:vector<HashData<K,V>> _table;size_t _n = 10;};
}//开散列的拉链法实现哈希表
namespace hash_bucket
{template<class K, class V>struct HashNode{HashNode(const pair<K,V>& kv = pair<K, V>()):_kv(kv),_next(nullptr){}pair<K, V> _kv;HashNode<K, V>* _next;};template<class K, class V, class HashFunc = DefaultHashFanc<K>>class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_table.resize(10, nullptr);_n = 0;}~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;}}bool Insert(const pair<K, V>& kv){if (Find(kv.first)){return false;}HashFunc hf;//负载因子到1就扩容if (_n == _table.size()){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 = hf(cur->_kv.first) % newSize;cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}}_table.swap(newTable);}size_t hashi = hf(kv.first) % _table.size();//链表头插Node* newnode = new Node(kv);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return true;}void Print(){for (size_t i = 0; i < _table.size(); i++){printf("[%d]->", i);Node* cur = _table[i];while (cur){cout << cur->_kv.first << ":"<<cur->_kv.second <<"->";cur = cur->_next;}printf("NULL\n");}}Node* Find(const K& key){HashFunc hf;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;}bool Erase(const K& key){HashFunc hf;size_t hashi = hf(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];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;}return false;}private:vector<Node*> _table;	//指针数组size_t _n; };
}

结语

希望这篇哈希表(hash_table)的模拟实现详解能对大家有所帮助,欢迎大佬们留言或私信与我交流.

学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!

相关文章推荐

【C++】模拟实现红黑树(RB-Tree)

【C++】模拟实现AVL树

【C++】模拟实现二叉搜索(排序)树

【C++】模拟实现priority_queue(优先级队列)

【C++】模拟实现queue

【C++】模拟实现stack

【C++】模拟实现list

【C++】模拟实现vector

【C++】模拟实现string类


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

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

相关文章

家里养有宠物应该用哪款宠物空气净化器比较好?哪款最能吸毛?

这不是国庆节刚过吗&#xff0c;我的小猫终于是平安的度过了在农村生活的时光&#xff0c;之前还担心会不会被爸妈嫌弃&#xff0c;这下好了&#xff0c;嫌弃也过了国庆节。 但是一把猫咪带回出租房&#xff0c;由于几天不在房子里待&#xff0c;猫咪对熟悉的环境又特别激动&a…

视频怎么做成扫码展示?视频二维码在线做的方法

视频想要快速的分享给其他人&#xff0c;选择生成二维码是一种很方便的形式&#xff0c;其他人只需要扫描二维码就可以在线查看视频&#xff0c;与其他分享方式相比更加的简单、方便。现在日常生活中有很多场景都会有视频二维码的应用&#xff0c;简化了获取视频的流程&#xf…

JavaEE: 深入解析HTTP协议的奥秘(3)

文章目录 HTTP认识 "报头"(Header)认识 "状态码"(status code) HTTP JavaEE: 深入解析HTTP协议的奥秘(2) 书接上文~ 认识 “报头”(Header) Header 的整体的格式是"键值对"结构. 每个键值对占一行,键和值之间使用分号分隔. Host 表示服务器主…

【基础篇】一个键值数据库包含什么?

背景 今天&#xff0c;在构造这个简单的键值数据库时&#xff0c;我们只需要关注整体架构和核心模块。这就相当于医学上在正式解剖人体之前&#xff0c;会先解剖一只小白鼠。我们通过剖析这个最简单的键值数据库&#xff0c;来迅速抓住学习和调优 Redis 的关键。 我们把这个简…

STM32外设应用知识详解

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

RKMEDIA画面质量调节-QP调节

QP是在视频采集编码过程中的量化参数&#xff0c;其值与画面质量成反比&#xff0c;即QP值越大画面质量越小&#xff0c;其具体调整方法如下&#xff1a; typedef struct rkVENC_RC_PARAM_S {RK_U32 u32ThrdI[RC_TEXTURE_THR_SIZE]; // [0, 255]RK_U32 u32ThrdP[RC_TEXTURE_TH…

如何基于 RLHF 来优化 ChatGPT 类型的大语言模型

&#x1f6b4;前言 对于ChatGPT来说&#xff0c;RLHF是其训练的核心。所谓RLHF&#xff0c;即Reinforcement Learning with Human Feedback&#xff0c;基于人类反馈的强化学习。这项技术通过结合模型自身的生成能力和人类专家的反馈&#xff0c;为改进文本生成质量提供了新的…

解决Android Studio中使用lombok插件错误: 找不到符号的问题

问题 主要是想节省实体类的set、get等方法&#xff0c;使用lombok报错如下&#xff1a; 解决方案 由于Android的限制&#xff0c;在Android中使用lombok兼容极其麻烦&#xff0c;如果你只是想减少set、get等代码可以直接使用kotlin的data class 示例 data class KotlinTes…

等级保护等保资料原件合集(word源资料)

第二章 系统定级与安全域 2.1 系统定级 2.1.1 不同等级的安全保护能力 2.1.2 重要信息系统 2.1.3 定级参考 2.2 安全域定义 2.2.1 安全域定义方法 2.2.2 安全域等级描述 第三章 实施方案设计 3.1 三级等保要求 3.2 基本要求的详细技术要求 3.2.1 物理安全 3.2.2 网…

Unity 从零开始的框架搭建1-1 unity中对象调用的三种方式的优缺点分析【干货】

该文章专栏是向QFrameWork作者凉鞋老师学习总结得来&#xff0c;吃水不忘打井人&#xff0c;不胜感激 Unity 框架搭建学习笔记1-1&#xff0c;前一个1代表凉鞋的第一季教程&#xff0c;后一个1代表该季第一篇我的文章 unity中对象调用的三种方式 方法调用&#xff0c;例如&…

Qt设计登录界面

优化登录框&#xff1a; 将两个按钮连接到槽函数 在构造函数中定义 connect(this->btn1,&QPushButton::clicked,this,&Logon::my_slot);connect(this->btn2,&QPushButton::clicked,this,&Logon::my_cancel); 定义登录按钮连接的槽函数 void Logon::my…

基于Java语言的充电桩平台+云快充协议+充电桩管理后台+充电桩小程序

软件架构 1、提供云快充底层桩直连协议&#xff0c;版本为云快充1.5&#xff0c;对于没有对接过充电桩系统的开发者尤为合适&#xff1b; 2、包含&#xff1a;启动充电、结束充电、充电中实时数据获取、报文解析、Netty通讯框架、包解析工具、调试器模拟器软件等&#xff1b;…

CMake 属性之目标属性

【写在前面】 CMake 可以通过属性来存储信息。它就像是一个变量&#xff0c;但它被附加到一些其他的实体上&#xff0c;像是一个目录或者是一个目标。例如一个全局的属性可以是一个有用的非缓存的全局变量。 在 CMake 的众多属性中&#xff0c;目标属性 ( Target Properties ) …

NodeJS智慧社区管理微信小程序-计算机毕业设计源码40623

摘 要 随着中国经济的飞速增长&#xff0c;消费者的智能化水平不断提高&#xff0c;许多智能手机和相关的软件正在得到更多的关注和支持。其中&#xff0c;智慧社区管理微信小程序更是深得社区人员的喜爱&#xff0c;它的出现极大地改善了社区人员的生活质量&#xff0c;同时&…

宠物咖啡馆在线体验:SpringBoot框架的创新应用

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

云微客AI直播矩阵,让小白轻松上手的必备直播利器

现在直播带货都已经杀疯了&#xff0c;在新趋势下&#xff0c;AI智能直播应运而生。AI智能直播相较于传统直播&#xff0c;直播模式对于场地的要求和人员的要求都相对较低&#xff0c;大大降低了我们的试错成本&#xff0c;同时直播矩阵系统也为企业和个人带来了低成本、高效率…

浅析基于双碳目标的光储充一体化电站状态评估技术

摘要&#xff1a;全国碳市场拉开了我国能源结构加速转型的大幕&#xff0c;催生了光伏、储能和新能源汽车等一批绿色产业的兴起&#xff0c;同时随着利好政策扶植和消费者的青睐&#xff0c;光伏、储能和新能源汽车市场均加快发展。但传统的充电桩和光伏电站都是分开建设&#…

如何在电脑上创建虚拟网卡

1.右键点击此电脑&#xff0c;选择——管理 2.选择设备管理器——网络适配器&#xff0c;在点击操作选择 添加过时硬件 3.点击 下一页 4.在这里选择网络适配器&#xff0c;点击下一页 5.选择微软的环回适配器 6.打开控制面板 7.点击网络和Internet 8.点击网络和共享中心 9…

一个读取CT图像序列,并进行表面重建的C++代码

这篇文章中&#xff0c;介绍使用VTK进行读取CT图像&#xff08;一个序列&#xff09;&#xff0c;然后进行表面重建。为什么不使用DCMTK呢&#xff1f;因为使用DCMTK需要一张一张读取&#xff0c;要自己写一个代码&#xff0c;还要创建一个容器来放读入的CT数据&#xff0c;比较…

亳州自闭症寄宿制学校,关注孩子的学习和生活

在特殊教育领域&#xff0c;自闭症儿童的教育与成长一直是社会各界关注的焦点。近年来&#xff0c;随着对自闭症认识的加深&#xff0c;越来越多的寄宿制学校应运而生&#xff0c;致力于为这些特殊的孩子提供全面、个性化的教育服务。在安徽亳州&#xff0c;这样的学校正努力为…