哈希表的底层实现(2)---C++版
目录
链地址法Separate Chaining——哈希桶的模拟实现
超大重点分析:
两种方法对比
由于在上次的哈希表的底层实现(1)---C++版已经详细的阐述了相关的结构和原理,哈希表的实现方法主要分为链地址法和开放定址法。开放定址法上次已经实现过了,这次我们实现一下链地址法。
链地址法Separate Chaining——哈希桶的模拟实现
哈希桶的结构和链表是完全一样的,我们这边选择在每个vector里面装入单链表就可以了,比较简单嘛,所以每个结点和成员都是指针。
#include<iostream>
#include<vector>
using namespace std;
template<class K>
struct Hashfunc//仿函数
{
int operator()(const K& key)
{
return (int)key;
}
};
struct Hashfunc<string>//结构体名字必须一致才省略模板
{
int operator()(const string& key)
{
int hashi = 0;
for (auto e : key)
{
hashi = hashi * 31;
hashi = hashi + e;
}
return hashi;
}
};
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 hash = Hashfunc<K>>
class Hashtable
{
typedef Hashnode<K, V> node;
public:
Hashtable()
{
_tables.resize(10, nullptr);//先初始化存有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)
{
hash ha;
// 负载因子==1扩容
if (n == _tables[size])
{
/*Hashtable<K, V> newHT;
newHT._tables.resize(_tables.size() * 2);
for (size_t i = 0; i < _tables.size(); i++)
{
node* cur = _tables[i];
while(cur)
{
newHT.Insert(cur->_kv);//用以前复用的逻辑有点浪费空间了
cur = cur->_next;
}
}*/
vector<node*>newht.resize(_tables.size() * 2, nullptr);
for (int i = 0; i < _tables.size(); i++)
{
node* cur = _tables[i];
while (cur)
{
node* next = cur->_next;
// 旧表中节点,挪动新表重新映射的位置
size_t hashi = ha(cur->_kv.first) % newht.size();
// 头插到新表,当然使用尾插也可以
cur->_next = newht[hashi];//头插的逻辑
newht[hashi] = cur;
cur = next;
}
_tables[i] = nullptr;//置空了头结点后面的结点也就找不到了,其实感觉不置空也没什么问题
}
_tables.swap(newht);//再交换一下
}
size_t hashi = ha(kv.first) % _tables.size();
//头插
node* newnode = new(kv);//通过kv构造一个新结点,需要合适的构造函数
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
n++;
}
Node* Find(const K& key)
{
hash he;
size_t hashi = he(K) % _tables.size();
node* cur = _table[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
hash ha;
if (Find(key) == nullptr)
{
return false;
}
else
{
size_t hashi =ha(K) % _tables.size();
node* cur = _table[hashi];
node* prev = nullptr;
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
cur = nullptr;
--n;
return true;
}
prev = cur;
cur = cur->_next
}
}
}
private:
vector<node*> _tables;// 使用指针数组
size_t n = 0;//负载因子
};
超大重点分析:
为什么需要自己写析构函数呢?因为如果让系统调用默认构造的话,成员中负载因子属于内置类型编译器不处理,然后vector属于自定义类型,编译器会调用vector的默认构造,这样vector里面的单链表就没有办法析构了,就会照成内存泄漏。
为什么扩容不复用insert了呢,先说一下为什么会需要扩容,随着数据的不断大量的插入单链表,肯定在某种情况下会使得某个链表过于长,这样在查找哈希表的时候会使得时间复杂度过于大了,所以引入负载因子n进行控制,当n == size时就扩容,为什么在扩容时不建议复用呢,因为这样不断的创造新的结点而放着旧结点不直接拿来用的话会比较浪费空间,创造一个结点的消耗还是比较大的。
为什么这边需要写构造函数,因为insert传的是pair,那根据这个pair构造新结点的话需要自己写一个构造,默认构造用不上。
为什么这边哈希桶状的结构是单链表而不是直接使用list或者C++11新加入的forward_list呢,首先没说不可以,但是用单链表不是更简单吗,forward_list尽量少用。
vector<list<pair<K, V>>> _tables; // 指针数组
像上面这种就是使用list的写法,但是到时候封装的iterator实现起来会比较困难
struct Bucket//联合体
{
list<pair<K, V>> _lt;
map<K, V> _m;
size_t _bucketsize; // >8 map <=8 list
};
vector<Bucket> _tables;
但是呢就算是有扩容操作还是会有人故意使用一些很极端的数据使得即使多次扩容还是显得某个链表的插入数据很多,导致每个链表插入数据的数目不平衡。所以为了解决这种情况,有些人会选择当负载因子过大时转而使用搜索树map来代替list实现存储,如上:
最后一个问题,为什么使用头插呢,因为其实无论是头插还是尾插在Find还是erase都没什么显著差别的,但是在扩容时头插会比尾插更有优势,因为每个结点刚开始初始化时的_next结点都是空
这样当头插到开头时每次指向的都是空,这样就不会把多余的结点带出来了,如果是尾插就需要最后再手动将_next置空。
两种方法对比
应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a ,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间。
相关文章:

哈希表的底层实现(2)---C++版
目录 链地址法Separate Chaining——哈希桶的模拟实现 超大重点分析: 两种方法对比 由于在上次的哈希表的底层实现(1)---C版已经详细的阐述了相关的结构和原理,哈希表的实现方法主要分为链地址法和开放定址法。开放定址法上次已经实现过了,…...

算法知识点————【LRU算法】
思想:淘汰最久没有使用的 应用场景:手机清后台的时候先清最久没有使用的应用 设计一种数据结构:接收一个 capacity 参数作为缓存的最大容量,然后实现两个 API,一个是 put(key, val) 方法存入键值对,另一个是…...

记一次MySQL视图查询优化的经验
背景:库房系统项目迁移,两个版本的结构发生了很大变化,新版本的库存系统在开发阶段由于数据量小,根据看不出查询的性能问题,还沾沾自喜的想新版本多好。但是在做同步之后(规则变更,需要插入很多…...

Cloudways搭建WordPress外贸独立站完整教程(1)
验证邮件发送完成后,就等待Cloudways的回复邮件,一般24小时之内就会收到激活的邮件。 Cloudways账号升级 激活成功后还需要账户升级,Cloudways提供了为期3天的免费试用体验。如果在试用期结束之前未绑定信用卡以升级账户,试用期…...

Delphi5数据控制组件——查询
文章目录 效果图参考查询Free方法Close方法总结通俗理解 完整代码 效果图 参考 本文是在上一篇的基础上,将查询页面重新写一次。 查询 {点击查询} procedure TForm2.Button1Click(Sender: TObject); vartj,tj1,tj2,tj3,tj4,tj5,tj6,tj7:string; begin//按照工号查…...

git pull之后发现项目错误,如何回到之前的版本方法
目录 首先我们打开小程序的cmd的黑窗口,git reflog查看之前的版本 之后再git reset --hard main{1} 我这个就已经返回了之前的6daaa2e的版本了 首先我们打开小程序的cmd的黑窗口,git reflog查看之前的版本 之后再git reset --hard main{1} 我这个就已…...

防跌倒识别摄像机
防跌倒识别摄像机 是一种结合了人工智能技术和监控摄像技术的先进设备,旨在通过实时监测和分析监控画面中的行为动作,及时发现并预防跌倒事件的发生。这种摄像机在医疗、养老院、家庭等场所有着广泛的应用前景。 防跌倒识别摄像机在医疗领域具有重要意义…...

MyQql性能诊断与实践
获取更多免费资料,见下图...

有序序列判断
描述 输入一个整数序列,判断是否是有序序列,有序,指序列中的整数从小到大排序或者从大到小排序(相同元素也视为有序)。 数据范围:3 < n< 50 序列中的值都满足 1< val < 100 输入描述: 第一行输入一个整数N…...

【Kubernetes知识点问答题】健康检查
目录 1. Kubernetes 对集群 Pod 和容器健康状态如何进行监控和检测的。 2. 解释 LivenessProbes 探针的作用及其适用场景。 3. 解释 ReadinessProbe 探针的作用及其适用场景。 4. 解释 StartupProbe 探针的作用及其适用场景。 5. 说明 K8s 中 Pod 级别的 Graceful Shutdown…...

【Prometheus】PromQL数据类型以及常用的计算函数用法详解
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…...

STM32高级定时器生成互补PWM的原理与代码实现
文章目录 前言一 CubeMx配置1.1 TIM1 Mode and Configuration1.2 Paramter Settings 二 程序代码三 仿真分析总结 前言 互补 PWM(Complementary PWM)是指一对逻辑状态互为反相的 PWM(脉冲宽度调制)信号。这种信号配置常见于电机控…...

双指针题总结
双指针题总结 hot100移动零盛水最多的容器三数之和接雨水最小覆盖子串 hot100 移动零 题目链接: 283.移动零 代码: class Solution {public void moveZeroes(int[] nums) {int slow 0;for (int fast 0; fast < nums.length; fast ){if (nums[fas…...

[数据集][目标检测]人脸口罩佩戴目标检测数据集VOC+YOLO格式8068张3类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):8068 标注数量(xml文件个数):8068 标注数量(txt文件个数):8068 标注…...

JVM3-双亲委派机制
目录 概述 作用 如何指定加载类的类加载器? 面试题 打破双亲委派机制 自定义类加载器 线程上下文类加载器 Osgi框架的类加载器 概述 由于Java虚拟机中有多个类加载器,双亲委派机制的核心是解决一个类到底由谁加载的问题 双亲委派机制ÿ…...

经典文献阅读之--DEviLOG(使用合成数据和真实世界数据的数据驱动占用网格映射基于Transformer的BEV方案量产方案)
0. 简介 在自动驾驶汽车(AV)的感知任务中,数据驱动的方法往往优于传统方法。这促使我们开发了一种基于数据的方法来从激光雷达测量中计算占用网格地图(OGM)。我们的方法扩展了之前的工作,使得估计的环境表…...

ssh之登录服务器后,自动进入目录(四十七)
简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【…...

如何看待IBM中国研发部裁员?
背景: 近日,IBM中国宣布撤出在华两大研发中心,引发了IT行业对于跨国公司在华研发战略的广泛讨论。这一决定不仅影响了众多IT从业者的职业发展,也让人思考全球化背景下中国IT产业的竞争力和未来发展方向。面对这一突如其来的变化&…...

计算机毕业设计选题推荐-土地承包管理系统-Java/Python项目实战(亮点:数据可视化分析、账号锁定、智能推荐)
✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…...

2024年高校辅导员考试题库及答案
一、判断题 121.高校学生身份权是基于高等教育的性质,学生应该获得的本体性权利。 答案:正确 122.学费占年生均教育培养成本的比例和标准由财政部制定。 答案:错误 123.享受国家专业奖学金的高校学生免缴学费。 答案:错误 124…...

使用python对股票市场进行数据挖掘的书籍资料有哪些
炒股自动化:申请官方API接口,散户也可以 python炒股自动化(0),申请券商API接口 python炒股自动化(1),量化交易接口区别 Python炒股自动化(2):获取…...

Python 将字典转换为 JSON
在 Python 中,可以使用 json 模块将字典转换为 JSON 格式的字符串。该模块提供了 json.dumps() 方法,用于将 Python 对象(如字典、列表)序列化为 JSON 字符串。 1、问题背景 用户想要将一个 Python 字典转换为 JSON 格式…...

就服务器而言,ARM架构与X86架构有什么区别?各自的优势在哪里?
一、服务器架构概述 在数字化时代,服务器架构至关重要。服务器是网络核心节点,存储、处理和提供数据与服务,是企业和组织信息化、数字化的关键基础设施。ARM 和 x86 架构为服务器领域两大主要架构,x86 架构服务器在市场占主导&…...

[论文笔记]Dimensionality Reduction by Learning an Invariant Mapping
引言 今天带来一篇真正远古(2005年)论文的笔记,论文是Dimensionality Reduction by Learning an Invariant Mapping。 该论文中提出的对比损失(2.1节)可以用于训练嵌入模型。 为了简单,下文中以翻译的口吻记录,比如替换"作者"为"我们"。 降维涉及将一…...

链表算法题(下)
在链表算法题(上)长中我们已经学习了一系列的链表算法题,那么在本篇中我们将继续来学习链表的算法题,接下来就继续来破解链表的算法题吧! 1.相交链表 160. 相交链表 - 力扣(LeetCode) 通过以上…...

UE4_后期处理_后期处理材质及后期处理体积二
效果: 步骤: 1、创建后期处理材质,并设置参数。 2、回到主界面,找到需要发光的物体的细节面板。 渲染自定义深度通道,默认自定义深度模具值为10(需要修改此值,此值影响物体的亮度)。 3、添加…...

Linux系统与高效进程控制的实战技巧
Linux系统与高效进程控制的实战技巧 Linux,作为一种开源的Unix-like操作系统内核,自1991年由芬兰程序员Linus Torvalds首次发布以来,已成为全球范围内广泛使用的操作系统之一。其强大的功能、灵活的配置以及高度的可定制性,使得L…...

陈文自媒体:抖音创作者伙伴计划,你不知道的几点!
本月的2号开始,官方就下达了通知,各位西瓜创作者,大家要抓紧时间升级为抖音创作者伙伴计划,如果你不升级是吧,没问题,19号开始不发西瓜和中视频收益了。 在这个政策解读和操作过程中,我从同行、…...

便携式气象仪器的主要特点
TH-BQX9】便携式气象仪器,也称为便携式气象仪或便携式自动气象站,是一款高度集成、低功耗、可快速安装、便于野外监测使用的高精度自动气象观测设备。以下是关于便携式气象仪器的详细介绍: 主要特点 高精度与多功能:便携式气象仪器…...

【开源风云】从若依系列脚手架汲取编程之道(四)
📕开源风云系列 🍊本系列将从开源名将若依出发,探究优质开源项目脚手架汲取编程之道。 🍉从不分离版本开写到前后端分离版,再到微服务版本,乃至其中好玩的一系列增强Plus操作。 🍈希望你具备如下…...