【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.rhkb.cn/news/443040.html

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

相关文章

Python【修炼2】

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;Python 目录 &#x1f449;&#x1f3fb;map&#x1f449;&#x1f3fb;lambda&#x1f449;&#x1f3fb;datetime日期输出格式 &#x1f449;&#x1f3fb…

Pikachu-PHP反序列化

从后端代码可以看出&#xff0c;拿到序列化后的字符串&#xff0c;直接做反序列化&#xff1b;并且在前端做了展示&#xff1b; 如果虚拟化后的字符串&#xff0c;包含alert 内容&#xff0c;反序列化后&#xff0c;就会弹出窗口 O:1:"S":1:{s:4:"test";s…

使用Provide和Inject设计Vue3插件

使用provide和inject的Vue依赖项注入非常适合构建Vue3插件或避免prop多层传递。 尽管不经常使用它&#xff0c;但是您可以仅使用两个内置方法来实现依赖项注入&#xff1a;provide和inject。 查看Composition API文档&#xff0c;在Vue 3.0中&#xff0c;使用Provide和Inject进…

Navicat下载安装

官网地址&#xff1a;Navicat | Download Navicat Premium 14-day trial versions for Windows, macOS and Linux 1、进入官网下载地址&#xff0c;根据需求进行下载 2、双击安装程序&#xff0c;点击【下一步】 3、选择【我同意】&#xff0c;点击下一步 4、自定义安装路径&a…

Linux基于CentOS学习【进程状态】【进程优先级】【调度与切换】【进程挂起】【进程饥饿】

目录 进程状态 状态决定了什么 进程等待方式——队列 进程状态的表现 挂起状态 基于阻塞的挂起——阻塞挂起 swap分区 进程状态表示 Z僵尸状态 进程的优先级 什么是进程的优先级 为什么会有进程的优先级 进程饥饿 Linux的调度与切换 切换 调度 queue [ 140 ]&am…

使用本地模型根据对话对客户进行画像

基于ollama部署本地模型&#xff0c;如&#xff1a;qwen2.5。通过迭代提示词实现客户画像的生成&#xff0c;根据具体需求&#xff0c;通过迭代提示词可以达成目标。输出的结果可以要求JSON格式输出&#xff0c;当前模型JSON的解析准确率比较高&#xff0c;在输出的content中&a…

【可视化大屏】将柱状图引入到html页面中

到这里还是用的死数据&#xff0c;先将柱状图引入html页面测试一下 根据上一步echarts的使用步骤&#xff0c;引入echarts.js后需要初始化一个实例对象&#xff0c;所以新建一个index.js文件来进行创建实例化对象和配置数据信息等。 //在index.html引入<script src"j…

案例分享—国外优秀UI设计作品赏析

国外深色界面UI设计的简洁感首先来源于其对色彩运用的精妙。深色背景能有效减少视觉干扰&#xff0c;使关键元素如文字、图标等更加突出。这种设计不仅提升了信息的可读性&#xff0c;还让界面显得更为简洁、清晰&#xff0c;用户能够更快地找到所需信息&#xff0c;提升使用体…

Linux,中文输入法、C/C++编译环境配置

一、Linux中文输入配置 1、点击设置中的区域与语言 2、此处无声胜有声 一定要选第一个汉语&#xff08; Intelligent pingying&#xff09;,要不然最后打不出来中文字 二、VScode C/C环境配置 先下载插件&#xff0c;中文插件和C/C环境插件 终端依次执行下列命令行&#xff0…

【自动驾驶汽车通讯协议】GMSL通信技术以及加串器(Serializer)解串器(Deserializer)介绍

文章目录 0. 前言1. GMSL技术概述2. 为什么需要SerDes&#xff1f;3. GMSL技术特点4.自动驾驶汽车中的应用5. 结论 0. 前言 按照国际惯例&#xff0c;首先声明&#xff1a;本文只是我自己学习的理解&#xff0c;虽然参考了他人的宝贵见解及成果&#xff0c;但是内容可能存在不准…

3D看车如何实现?有哪些功能特点和优势?

3D看车是一种创新的汽车展示方式&#xff0c;它利用三维建模和虚拟现实技术&#xff0c;将汽车以更真实、更立体的形式呈现在消费者面前。 一、3D看车的实现方式 1、三维建模&#xff1a; 通过三维建模技术&#xff0c;按照1:1的比例还原汽车外观&#xff0c;包括车身线条、细…

C语言 | Leetcode C语言题解之第452题用最少数量的箭引爆气球

题目&#xff1a; 题解&#xff1a; int cmp(void* _a, void* _b) {int *a *(int**)_a, *b *(int**)_b;return a[1] < b[1] ? -1 : 1; }int findMinArrowShots(int** points, int pointsSize, int* pointsColSize) {if (!pointsSize) {return 0;}qsort(points, pointsSi…

七氟烷麻醉药市场研究:未来几年年复合增长率CAGR为4.2%

七氟烷是一种吸入麻醉剂&#xff0c;用于在外科手术过程中诱导和维持全身麻醉。七氟烷是一种挥发性麻醉剂&#xff0c;常用于在外科手术过程中诱导和维持全身麻醉。它因起效快和作用消失快而受到青睐&#xff0c;是成人和儿科患者的理想选择。七氟烷通常通过吸入起作用&#xf…

linux-冯诺伊曼体系结构以及操作系统

冯诺依曼体系结构 我们不畅见到计算机&#xff0c;如笔记本&#xff0c;不常见的如服务器&#xff0c;大部分都遵循着冯诺伊曼体系结构 截至目前&#xff0c;我们所认识的计算机&#xff0c;都是由一个个硬件组件组成。 输入单元&#xff1a;包括键盘 , 鼠标&#xff0c;扫描…

文件防泄密措施措施有哪些?5种文件防泄密措施等你体验!【小白成长篇!】

“千里之堤&#xff0c;溃于蚁穴。” 这句谚语告诉我们&#xff0c;再坚固的防线也可能因为一个小小的疏忽而崩溃。 在信息安全领域&#xff0c;文件泄密同样如此。 一个小小的失误&#xff0c;就可能导致企业的核心机密外泄&#xff0c;造成无法挽回的损失。 因此&#xff…

人脸表情行为识别系统源码分享

人脸表情行为识别系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

初入网络学习第一篇

引言 不磨磨唧唧&#xff0c;跟着学就好了&#xff0c;这个是我个人整理的学习内容梳理&#xff0c;学完百分百有收获。 1、使用的网络平台:eNSP 下载方法以及内容参考这篇文章 华为 eNSP 模拟器安装教程&#xff08;内含下载地址&#xff09;_ensp下载-CSDN博客https://b…

15分钟学 Python 第37天 :Python 爬虫入门(三)

Day 37 : Python爬虫入门大纲 章节1&#xff1a;Python爬虫概述 1.1 什么是爬虫&#xff1f; 网页爬虫&#xff08;Web Crawler&#xff09;是一种自动访问互联网上网页并提取数据的程序。爬虫的作用包括搜索引擎索引内容、市场调查、数据分析等。 1.2 爬虫的工作原理 发起…

计算机毕业设计 基于Django的在线考试系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

考研报名记录冲冲冲

研究生报名 网址 https://yz.chsi.com.cn/apply/ 报名包括网上报名和网上确认两个阶段&#xff0c;所有考生均须在规定时间内参加网上报名和网上确认。网上报名时间为2024年10月15日至10月28日&#xff08;网上预报名时间为2024年10月9日至10月12日&#xff0c;网上预报名和正…