C++哈希表的实现

C++哈希表的实现

  • 一.unordered系列容器的介绍
  • 二.哈希介绍
    • 1.哈希概念
    • 2.哈希函数的常见设计
    • 3.哈希冲突
    • 4.哈希函数的设计原则
  • 三.解决哈希冲突
    • 1.闭散列(开放定址法)
      • 1.线性探测
        • 1.动图演示
        • 2.注意事项
        • 3.代码的注意事项
        • 4.代码实现
    • 2.开散列(哈希桶,拉链法)
      • 1.概念
      • 2.动图演示
      • 3.增容问题
        • 1.拉链法的负载因子
          • 2.说明
    • 3.开散列和闭散列的比较
  • 四.开散列哈希表的实现
    • 1.跟闭散列哈希表相同的部分
    • 2.析构,查找,删除
      • 1.析构
      • 2.查找
      • 3.删除
    • 3.插入
      • 1.不扩容的代码
      • 2.扩容代码
      • 3.插入的完整代码
    • 4.开散列哈希表的完整代码

一.unordered系列容器的介绍

在这里插入图片描述
在这里插入图片描述

二.哈希介绍

1.哈希概念

在这里插入图片描述

2.哈希函数的常见设计

在这里插入图片描述

3.哈希冲突

在这里插入图片描述

4.哈希函数的设计原则

在这里插入图片描述

三.解决哈希冲突

解决哈希冲突两种常见的方法是:闭散列和开散列

1.闭散列(开放定址法)

在这里插入图片描述
因为线性探测跟二次探测很像,所以这里就只实现线性探测了

1.线性探测

1.动图演示

在这里插入图片描述
在这里插入图片描述

2.注意事项

在这里插入图片描述

3.代码的注意事项

1.仿函数的问题:
(1).因为string类型不能进行取模运算,因此给string类型增加一个仿函数
该仿函数可以将string转为整型,整型可以进行取模运算
因此这就相当于二层映射
string -> int -> 哈希表中的下标
(2)
因为这里要考虑到顺序问题,比如"abc",“acb”
或者ASCII码值相等的问题:“aad”,“abc”
所以很多大佬设计了很多字符串哈希算法
各种字符串Hash函数
大家感兴趣的话可以看这篇博客当中的介绍

(3)因为string类型的哈希映射太常用了,
所以这里使用了模板特化,以免每次要存放string时都要指名传入string的哈希函数

这里的哈希函数只返回了整形值,计算下标时一定不要忘了对哈希表大小取模
否则就会有vector的越界错误,直接assert断言暴力报错了

哈希表是Key-Value模型
哈希下标是按照Key来计算的

//仿函数
//整型的hash函数
template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};
//模板特化
//string的哈希函数
template<>
struct HashFunc<string>
{size_t operator()(const string& key){// BKDR字符串哈希函数size_t hash = 0;for (auto e : key){hash *= 131;hash += e;}return hash;}
};template<class K, class V, class Hash = HashFunc<K>>
class HashTable
....
4.代码实现
namespace open_address
{enum Status{EMPTY,EXIST,DELETE};template<class K, class V>struct HashData{pair<K, V> _kv;Status _s;         //状态};//仿函数//整型的hash函数template<class K>struct HashFunc{size_t operator()(const K& key){return (size_t)key;}};//模板特化//string的哈希函数template<>struct HashFunc<string>{size_t operator()(const string& key){// BKDR字符串哈希函数size_t hash = 0;for (auto e : key){hash *= 131;hash += e;}return hash;}};template<class K, class V, class Hash = HashFunc<K>>class HashTable{public:HashTable(){_tables.resize(10);}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();}_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();while (_tables[hashi]._s != EMPTY){if (_tables[hashi]._s == EXIST&& _tables[hashi]._kv.first == key){return &_tables[hashi];}hashi++;hashi %= _tables.size();}return NULL;}// 伪删除法bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret){ret->_s = DELETE;--_n;return true;}else{return false;}}private:vector<HashData<K, V>> _tables;size_t _n = 0; // 存储的关键字的个数};
}

2.开散列(哈希桶,拉链法)

上面的闭散列并不好用,因此我们重点介绍和实现开散列方法

1.概念

在这里插入图片描述

2.动图演示

插入之前:
在这里插入图片描述
插入过程:
在这里插入图片描述
插入之后:
在这里插入图片描述

3.增容问题

1.拉链法的负载因子

在这里插入图片描述
注意:扩容时因为要将下标重新映射所以扩容会使一个桶当中的数据被打散到不同的桶当中,使得这种极端情况很难发生

2.说明

1.对于这里的哈希桶我们采用单链表
2.为了后续使用开散列的哈希桶封装unordered_set和unordered_map,我们不使用STL库中的forward_list(C++11新增容器:单链表),而是自己手撕单链表
3.因为这里的单链表是我们自己实现的,所以要写析构函数,不能使用编译器默认生成的析构函数
4.为了提高效率,哈希表增容时我们直接转移节点,并不会去进行节点的深拷贝,那样太浪费空间了
5.开散列的哈希表无非就是一个指针数组而已,所以大家不要有任何的害怕
AVL树和红黑树我们都能实现,哈希表怕什么…

3.开散列和闭散列的比较

在这里插入图片描述

四.开散列哈希表的实现

1.跟闭散列哈希表相同的部分

namespace wzs
{//HashFunc<int>template<class K>//整型的哈希函数struct HashFunc{size_t operator()(const K& key){return (size_t)key;}};//HashFunc<string>//string的哈希函数template<>struct HashFunc<string>{size_t operator()(const string& key){// BKDRsize_t hash = 0;for (auto e : key){hash *= 131;hash += e;}return hash;}};template<class K, class V>struct HashNode{HashNode* _next;pair<K, V> _kv;HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr){}};template<class K, class V, class Hash = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_tables.resize(10);}~HashTable();bool Insert(const pair<K, V>& kv);Node* Find(const K& key);bool Erase(const K& key);private://哈希表是一个指针数组vector<Node*> _tables;size_t _n = 0;Hash hash;};
}

2.析构,查找,删除

1.析构

析构就是遍历哈希表,把每个单链表都销毁即可

~HashTable()
{for (int i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}
}

2.查找

1.根据哈希函数计算出下标,找到对应的哈希桶
2.遍历哈希桶,找数据即可
3.找到则返回该节点,找不到返回空指针

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

3.删除

删除就是找到该节点,让该节点的前驱指向该节点的后继,然后delete该节点
注意:
如果该节点是该哈希桶的头节点,直接让该哈希桶的头节点成为该节点的后继,然后delete该节点即可

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

3.插入

因为我们的哈希表不支持存放重复值,所以插入时
1.先查找在不在,如果在,返回false表示插入失败
2.不在,判断是否需要扩容,如果需要,则进行扩容
3.插入时,先根据哈希函数计算出对应的下标,然后找到该哈希桶头插即可

1.不扩容的代码

bool Insert(const pair<K, V>& kv)
{//先查找在不在//如果在,返回false,插入失败if (Find(kv.first)){return false;}//扩容....//1.利用哈希函数计算需要插入到那个桶里面int hashi = hash(kv.first) % _tables.size();//头插Node* newnode = new Node(kv);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;
}

2.扩容代码

1.开辟新的哈希表
2.将新哈希表的容量扩为2倍(一定要做,因为转移数据时需要根据新表的大小映射下标)
3.转移数据时
(1).遍历旧表取节点
(2).利用哈希函数计算该节点在新表中的下标
(3).头插该节点
4.转移完数据后不要忘记把旧表中的哈希桶的节点置空,否则会出现野指针问题

bool Insert(const pair<K, V>& kv)
{//扩容if (_n == _tables.size()){//开辟新的哈希表HashTable newtable;int newcapacity = _tables.size() * 2;//扩2倍newtable._tables.resize(newcapacity);//转移数据for (int i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;int hashi = hash(cur->_kv.first) % newtable._tables.size();cur->_next = newtable._tables[hashi];newtable._tables[hashi] = cur;cur = next;}//防止出现野指针导致重复析构..._tables[i] = nullptr;}}
}

3.插入的完整代码

bool Insert(const pair<K, V>& kv)
{//先查找在不在//如果在,返回false,插入失败if (Find(kv.first)){return false;}//扩容if (_n == _tables.size()){//开辟新的哈希表HashTable newtable;int newcapacity = _tables.size() * 2;//扩2倍newtable._tables.resize(newcapacity);//转移数据for (int i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;int hashi = hash(cur->_kv.first) % newtable._tables.size();cur->_next = newtable._tables[hashi];newtable._tables[hashi] = cur;cur = next;}//防止出现野指针导致重复析构..._tables[i] = nullptr;}//交换两个vector,从而做到交换两个哈希表//通过学习vector的模拟实现,我们知道vector进行交换时只交换first,finish,end_of_storage_tables.swap(newtable._tables);}//1.利用哈希函数计算需要插入到那个桶里面int hashi = hash(kv.first) % _tables.size();//头插Node* newnode = new Node(kv);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;
}

4.开散列哈希表的完整代码

namespace wzs
{//HashFunc<int>template<class K>//整型的哈希函数struct HashFunc{size_t operator()(const K& key){return (size_t)key;}};//HashFunc<string>//string的哈希函数template<>struct HashFunc<string>{size_t operator()(const string& key){// BKDRsize_t hash = 0;for (auto e : key){hash *= 131;hash += e;}return hash;}};template<class K, class V>struct HashNode{HashNode* _next;pair<K, V> _kv;HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr){}};template<class K, class V, class Hash = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_tables.resize(10);}~HashTable(){for (int i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}bool Insert(const pair<K, V>& kv){//先查找在不在//如果在,返回false,插入失败if (Find(kv.first)){return false;}//扩容if (_n == _tables.size()){//开辟新的哈希表HashTable newtable;int newcapacity = _tables.size() * 2;//扩2倍newtable._tables.resize(newcapacity);//转移数据for (int i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;int hashi = hash(cur->_kv.first) % newtable._tables.size();cur->_next = newtable._tables[hashi];newtable._tables[hashi] = cur;cur = next;}//防止出现野指针导致重复析构..._tables[i] = nullptr;}//交换两个vector,从而做到交换两个哈希表//通过学习vector的模拟实现,我们知道vector进行交换时只交换first,finish,end_of_storage_tables.swap(newtable._tables);}//1.利用哈希函数计算需要插入到那个桶里面int hashi = hash(kv.first) % _tables.size();//头插Node* newnode = new Node(kv);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}Node* Find(const K& key){int hashi = hash(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;}bool Erase(const K& key){int hashi = hash(key) % _tables.size();Node* cur = _tables[hashi], * prev = nullptr;while (cur){if (cur->_kv.first == key){if (cur == _tables[hashi]){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}return true;}prev = cur;cur = cur->_next;}return false;}private://哈希表是一个指针数组vector<Node*> _tables;size_t _n = 0;Hash hash;};
}

以上就是C++哈希表的实现的全部内容,希望能对大家有所帮助!

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

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

相关文章

Linux命令-查看内存、GC情况及jmap 用法

查看进程占用内存、CPU使用情况 1、查看进程 #jps 查看所有java进程 #top 查看cpu占用高进程 输入m &#xff1a;根据内存排序 topMem: 16333644k total, 9472968k used, 6860676k free, 165616k buffers Swap: 0k total, 0k used, 0k free, 6…

网络爬虫之Ajax动态数据采集

动态数据采集 规则 有时候我们在用 requests 抓取页面的时候&#xff0c;得到的结果可能和在浏览器中看到的不一样&#xff0c;在浏览器中可以看到正常显示的页面教据&#xff0c;但是使用 requests 得到的结果并没有&#xff0c;这是因为requests 获取的都是原始的 HTML 文档…

LeetCode刷题--- 目标和

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 http://t.csdnimg.cn/6AbpV 数据结构与算法 http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主要讲述递归递归、搜…

数值分析期末复习

第一章 科学计算 误差 解题步骤 先求绝对误差: ∣ x − x ∗ ∣ |x - x^*| ∣x−x∗∣求相对误差限: ∣ x − x ∗ ∣ x ∗ \frac{|x\,\,-\,\,x^*|}{x^*} x∗∣x−x∗∣​求有效数字 ∣ x − x ∗ ∣ 需要小于它自身的半个单位 |x-x^*|\text{需要小于它自身的半个单位} ∣…

亚信安慧AntDB:支撑中国广电5G业务的数据库之力

自2019年6月获得5G牌照以来&#xff0c;中国广电积极利用700MHz频谱资源&#xff0c;迅速崛起为第四大运营商&#xff0c;标志着其在数字通信领域取得的巨大成就。通过与中国移动紧密合作&#xff0c;共建共享基站已超过400万座&#xff0c;为实现自主运营和差异化竞争提供了坚…

直接插入排序【从0-1学数据结构】

文章目录 &#x1f497; 直接插入排序Java代码C代码JavaScript代码稳定性时间复杂度空间复杂度 我们先来学习 直接插入排序, 直接排序算是所有排序中最简单的了,代码也非常好实现,尽管直接插入排序很简单,但是我们依旧不可以上来就直接写代码,一定要分析之后才开始写,这样可以提…

微软官方出品:GPT大模型编排工具,支持C#、Python等多个语言版本

随着ChatGPT的火热&#xff0c;基于大模型开发应用已经成为新的风口。虽然目前的大型模型已经具备相当高的智能水平&#xff0c;但它们仍然无法完全实现业务流程的自动化&#xff0c;从而达到用户的目标。 微软官方开源的Semantic Kernel的AI编排工具&#xff0c;就可以很好的…

设计模式(三)-结构型模式(6)-享元模式

一、为何需要享元模式&#xff08;Flyweight&#xff09;? 假如在网页中渲染这样的一个画面&#xff1a;大小不一的星星铺满了整个画布&#xff0c;并且都在不断的进行移动闪烁着。一批星星消失了&#xff0c;另一批又从另一边缘处出现。 要实现这样的渲染效果&#xff0c;在…

实习课知识整理2:用户登录及实现登录后用户名和头像的展示

接上一篇&#xff0c;当用户点击购买按钮后&#xff0c;还是未登录的状态&#xff0c;此时页面会跳转到登录页面&#xff0c;这时需要输入正确的用户名和密码&#xff0c;完成登录 1. 给登录按钮添加点击事件&#xff0c;并提交表单中的数据到后端 <form th:action"{/u…

Elasticsearch Reroute API 的使用

本文通过一个 Elasticsearch 集群中主分片分配不均衡的例子演示一下 Cluster reroute API 的使用。 对于 Elasticsearch 分片分配策略不了解的同学可以点一下关注&#xff0c;后面更文之后获取第一手资料。 环境信息 Windows 10 Elasticsearch 8.1 JDK17 初始集群状态 分片…

【JAVA面试题】什么是引用传递?什么是值传递?

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 前言 博客的正文部分可以详细介绍Java中参数传递的机制&#xff0c;强调Java是按值传递的&#xff0c;并解释了基本数据类型和对象引用在这种传…

鳄鱼目标检测数据集VOC格式100张

鳄鱼是一种生活在热带和亚热带地区的爬行动物&#xff0c;属于爬行纲鳄形目鳄鱼科。它们的体形庞大&#xff0c;有粗壮的四肢和强壮的尾巴&#xff0c;一般能长到2-6米长&#xff0c;体重可达500公斤以上。鳄鱼的皮肤粗糙&#xff0c;呈灰褐色或黑色&#xff0c;布满了坚韧的鳞…

XSKY星辰天合星海架构荣获 IT168 “2023 年度技术卓越奖”

近日&#xff0c;"2023 年度技术卓越奖"获奖名单公布&#xff0c;XSKY 星辰天合的星海架构&#xff08;XSEA&#xff0c;极速全共享架构&#xff09;获得行业 CIO/CTO 大咖、技术专家及 IT 媒体三方认可&#xff0c;成功入选&#xff01; “技术卓越奖”评选由国内著…

【Linux驱动】字符设备驱动程序框架 | LED驱动

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《RTOS学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3c0;Hello驱动程序⚽驱动程序框架⚽编程 &#x1f3c0;LED驱动⚽配置GPIO⚽编程驱动…

最小二乘法简介

最小二乘法简介 1、背景描述2、最小二乘法2.1、最小二乘准则2.2、最小二乘法 3、最小二乘法与线性回归3.1、最小二乘法与线性回归3.2、最小二乘法与最大似然估计 4、正态分布&#xff08;高斯分布&#xff09; 1、背景描述 在工程应用中&#xff0c;我们通常会用一组观测数据去…

电商数仓项目----笔记六(数仓ODS层)

ODS层的设计要点如下&#xff1a; &#xff08;1&#xff09;ODS层的表结构设计依托于从业务系统同步过来的数据结构。 &#xff08;2&#xff09;ODS层要保存全部历史数据&#xff0c;故其压缩格式应选择压缩比较高的&#xff0c;此处选择gzip。 &#xff08;3&#xff09;…

C++入门-【13-C++ 多维数组】

C 多维数组 C 支持多维数组。多维数组声明的一般形式如下&#xff1a; type name[size1][size2]...[sizeN]; 例如&#xff0c;下面的声明创建了一个三维 5 . 10 . 4 整型数组&#xff1a; int threedim[5][10][4]; 二维数组 多维数组最简单的形式是二维数组。一个二维数组&am…

用23种设计模式打造一个cocos creator的游戏框架----(二十三)中介者模式

1、模式标准 模式名称&#xff1a;中介者模式 模式分类&#xff1a;行为型 模式意图&#xff1a;用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立地改变它们之间的交互。 结构图&#xff…

竞赛保研 基于GRU的 电影评论情感分析 - python 深度学习 情感分类

文章目录 1 前言1.1 项目介绍 2 情感分类介绍3 数据集4 实现4.1 数据预处理4.2 构建网络4.3 训练模型4.4 模型评估4.5 模型预测 5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于GRU的 电影评论情感分析 该项目较为新颖&#xff0c;适合作为竞…

Linux基本指令(一)

前言 基本知识 文件文件内容文件属性(对文件的操作就是对这两部分进行操作) 在Linux中以 . 开头的文件叫隐藏文件 以-开头的是普通文件 以d开头的是目录文件 几个指令 先快速认识几个指令&#xff0c;方便后续的详细介绍 whoami 查看当前使用Linux系统的用户是谁 pwd …