当前位置: 首页 > news >正文

【C++高阶(五)】哈希思想--哈希表哈希桶

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C++从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习C++
  🔝🔝


在这里插入图片描述

哈希结构

  • 1. 前言
  • 2. unordered系列容器
  • 3. 哈希概念以及哈希结构
  • 4. 哈希表详解(闭散列)
  • 5. 哈希表模拟实现
  • 6. 哈希桶详解(开散列)
  • 7. 哈希桶模拟实现
  • 8. 对于哈希结构的思考

1. 前言

相信大家一定听说过大名鼎鼎的
哈希结构吧,就算是没用过,也听说
过这句话:这道题无脑哈希就能做

哈希,哈希,到底什么是哈希?本篇文章
将带大家彻底搞懂这个问题!

本章重点:

本篇文章着重讲解关联式容器
unordered_map&set的底层结构
以及它们的模拟实现.并且将给大家
介绍unorder系列的接口函数!


2. unordered系列容器

不知道大家在刷题时有没有看见过
unordered_map和unordered_set
它们与map&set是什么关系?
什么时候可以用unordered系列?

带着这些疑问,进行今天的学习:
在这里插入图片描述

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
  2. 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。

可以发现,其实unordered_map和
map使用起来没什么区别,可以说
是一模一样,那么什么时候应该用
unordered系列呢?答案是你只关
心键值对的内容而不关心是否有序
时,选择unordered系列

同理,unordered_set和set的用法
也基本一致,这里就不多做介绍了
如果你不知道map和set的用法,请
先看这篇文章:

map和set的熟悉


3. 哈希概念以及哈希结构

unordered_map&set的底层
结构实际上是哈希桶,也就是
哈希结构,下面来了解一下哈希思想:

最简易的哈希思想,数组下标0到100
存储的值代表数字0到100存不存在

在这里插入图片描述

当然,实际情况下不可能最大值是几
就开辟多大的数组,因为会造成空间
的浪费,哈希的思路一般是根据某种
映射关系,把数据映射到数组中,查找
时也使用同样的映射关系来查找!

在这里插入图片描述
当然,当插入4后再插入14,此时会有问题
因为4这个位置已经被占用了,再次映射到
这个位置明显是行不通的,这个过程被称为
哈希冲突,具体内容会在后面讲解!

哈希结构又分为哈希表和哈希桶
下面就来一一讲解这两个的区别


4. 哈希表详解(闭散列)

引起哈希冲突的一个原因可能是:
哈希函数设计不够合理

在这里插入图片描述
然而不管哈希函数再怎么设计,都不能
完全保证不同的值映射到同一位置,所以
引申出了闭散列和开散列的解决方法

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去

寻找下一个空位置的方法有很多,如
线性探测(挨个往后找)
二次探测(以2^i为单位向后找)

这里只讲解线性探测

在这里插入图片描述
插入44后,位置4被占用了就往后找空位

哈希表的删除以及查找操作:

哈希表中的元素如果只是原生数据类型,
那么我们将4删除后,再去查找4肯定是找
不到的,但是此时去查找44也会找不到,因
为44本来应该映射到4位置,但是由于哈希
冲突跑到了8位置,并且我们并不知道它在
哪个位置,所以查找时会找不到!

解决方案:

存储数据不单单存储原生类型
再给每一个位置加上一个状态枚举
分别代表此位置是空,被删除还是有数

// 哈希表每个空间给个标记
// EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
enum State {EMPTY, EXIST, DELETE};

查找元素时,若此位置是删除或存在
状态就继续向后找,若是空就代表此
元素并不在哈希表中!


5. 哈希表模拟实现

首先我们先将整个结构框架写出来:

enum State
{EMPTY,EXIST,DELETE
};template<class K, class V>
struct HashData
{pair<K, V> _kv;State _state;HashData(const pair<K, V>& kv = make_pair(0, 0)):_kv(kv),_state(EMPTY){ }
};template<class K, class V>
class HashTable
{
private:vector<HashData<K, V>> _table;//数组中存储HashData封装的数据size_t _size = 0; //有效数据的个数
};

再来探讨一下插入时的扩容规则:

由于哈希表采用的是向后探测的方法
来存放不同的数据,那么当数据的个数
和数组的大小很接近时,会有很多冲突,
所以在容量到0.7或0.8时就应该要扩容了!
并且在扩容后,数据要重新根据先有的规则
进行挪动,也就是将旧数据挪动到新表!

bool insert(const pair<K, V>& kv)
{if (_table.size() == 0 || 10 * _size / _table.size() >= 7) // 扩容{size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;HashTable<K, V> newHT;newHT._table.resize(newSize);// 旧表的数据映射到新表for (auto e : _table){if (e._state == EXIST){newHT.insert(e._kv);}}_table.swap(newHT._table);}size_t index = kv.first % _table.size();//不能模capacity,如果模出来的数大于size了还插入进去了会报错//线性探测while (_table[index]._state == EXIST){index++;index %= _table.size();//过大会重新回到起点}_table[index]._kv = kv;_table[index]._state = EXIST;_size++;return true;
}HashData<K, V>* find(const K& key)
{if (_table.size() == 0)return nullptr;size_t index = key % _table.size();//负数会提升成无符号数,所以负数不影响结果,但是string类不能取模,需要加入一个仿函数size_t start = index;while (_table[index]._state != EMPTY){if (_table[index]._kv.first == key && _table[index]._state == EXIST)return &_table[index];index++;index %= _table.size();if (index == start)//全是DELETE时,必要时会breakbreak;}return nullptr;
}bool erase(const K& key)
{HashData<K, V>* ret = find(key);if (ret){ret->_state = DELETE;--_size;return true;}return false;
}

6. 哈希桶详解(开散列)

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中

哈希桶实际上是这样的结构:

在这里插入图片描述

看似是一格数据,其实是一个链表指针

并且开散列的扩容旧不需要像
闭散列一样到0.7旧扩容了

在这里插入图片描述

可以把数组的每一个位置想象成
一个抽屉,当你远观时它就是一个
单一的格子,当你仔细把玩时它就
是一个可以拉开的存储结构!


7. 哈希桶模拟实现

首先先把基础框架写出来:

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;
private:vector<Node*> _table;size_t _size = 0;//有效数据个数
};

下一步,将新来的元素头插到链表中
因为头插的效率是O(1),并且扩容后
的策略和哈希表一样,重新将数据映射
到新表中

bool insert(const pair<K, V>& kv)
{//去重+扩容if (find(kv.first))return false;//负载因子到1就扩容if (_size == _table.size()){vector<Node*> newT;size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;newT.resize(newSize, nullptr);//将旧表中的节点移动到新表for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t hashi = cur->_kv.first % newT.size();cur->_next = newT[hashi];newT[i] = cur;cur = next;}_table[i] == nullptr;}_table.swap(newT);}size_t hashi = kv.first % _table.size();//头插Node* newnode = new Node(kv);newnode->_next = _table[hashi];_table[hashi] = newnode;++_size;return true;
}Node* find(const K& key)
{if (_table.size() == 0)return nullptr;size_t hashi = 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)
{Node* ret = find(key);if (ret == nullptr)return false;size_t hashi = key % _table.size();Node* cur = _table[hashi];Node* prev = nullptr;while (cur && cur->_kv.first != key)//找到要删除的节点{prev = cur;cur = cur->_next;}Node* next = cur->_next;if (cur == _table[hashi])//注意头删的情况_table[hashi] = next;elseprev->_next = next;delete cur;cur = nullptr;_size--;return true;
}

对代码的解释都在注释中,还有问题欢迎私信!


8. 对于哈希结构的思考

我们会发现一个问题,不管是哈希
表还是哈希桶,都用到了cur.first模
上一个数,但是如果cur.first不是整型
不能取模怎么办?(如字符串)

这时需要在哈希类中再传入一个模板
参数,此模板参数为仿函数,只需将写好
的仿函数传入即可进行取模,比如string
仿函数可以这样写:

template<>
struct HashFunc<string>
{//BKDR算法:将字符串转换为整数size_t operator()(const string& str){size_t sum = 0;for (auto ch : str){sum *= 131;sum += (size_t)ch;}return sum;//将字符的asc码全部加起来再返回}
};

🔎 下期预告:哈希思想的应用🔍

相关文章:

【C++高阶(五)】哈希思想--哈希表哈希桶

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; 哈希结构 1. 前言2. unordered系列容器3. 哈希概…...

45、Flink 的指标体系介绍及验证(1)-指标类型及指标实现示例

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…...

SAP创建ODATA服务-Structure

SAP创建ODATA服务-Structure 1、创建数据字典 进入se11创建透明表ZRICO_USR,并创建对应字段 2、创建OData service 首先创建Gateway service project&#xff0c;事务码&#xff1a;SEGW&#xff0c;点击Create Project 按钮 Gateway service Project分四个部分&#xff1a…...

【开源】基于JAVA的车险自助理赔系统

项目编号&#xff1a; S 018 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S018&#xff0c;文末获取源码。} 项目编号&#xff1a;S018&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 角色管理模块2.3 车…...

单例模式-C++实现

目录 饿汉式懒汉式双检查锁&#xff0c;线程安全的版本什么是reorder&#xff1f;解决内存读写reorder不安全方法代码解释懒汉式的优缺点 单例模式是一种设计模式&#xff0c;用于确保一个类只有一个实例&#xff0c;并提供一个全局的访问点来获取该实例。它常用于需要在整个应…...

一种模板类实现和声明分开在生成的.a文件被使用时出现undefined reference时的一种解决方法

一种模板类实现和声明分开在生成的.a文件被使用时出现undefined reference时的一种解决方法 模板类头文件格式如下&#xff1a; test.h // test.h namespace test { namespace _testspace { class base { public: base(); ~base(); };template<bool T> class base_impl…...

js用到的算法

1.对象数组中&#xff0c;对象中有对象&#xff0c;数组根据对象中的对象打平 [{indexValueMap: { 68443: 0, 68457: 0 },rowName1: 固定收益类,rowName2: 交易类,rowName3: 次级},{indexValueMap: { 68443: 0, 68457: 0 },rowName1: 固定收益类,rowName2: 交易类,rowName3: 中…...

【科技素养】蓝桥杯STEMA 科技素养组模拟练习试卷9

1、商标也属于知识产权的一种。一个商标在注册之后&#xff0c;将会在&#xff08;&#xff09;的时间受到保护 A、20 年内 B、50 年内 C、直至注册人去世 D、10 年内 答案&#xff1a;D 2、人类史上第一位进入太空的宇航员是&#xff08;&#xff09;&#xff0c;他/她是…...

如何使用抖音直播调试入口扫码进行调试

使用抖音直播调试入口扫码进行调试的步骤如下&#xff1a; 确保你已经安装了抖音调试助手。打开调试助手&#xff0c;并在主界面点击“连接”按钮。在连接向导页面&#xff0c;根据提示连接你的抖音直播间。请确保你已经获取了直播间的token和scheme。连接成功后&#xff0c;你…...

AI智能人机对话小程序系统源码 附带完整的搭建教程

移动互联网的普及和快速发展&#xff0c;小程序已经成为了一种非常流行的应用形态。小程序具有即用即走、轻量级的特点&#xff0c;非常适合用于提供各种便捷服务。下面罗峰来给大家分享一款AI智能人机对话小程序系统源码&#xff0c;带有完整的搭建教程。 以下是部分代码示例…...

【腾讯云云上实验室】用向量数据库在金融信数据库分析中的实战运用

一、前言 这篇文章将带领读者探索数据库的多样化解决方案及其演进历程&#xff0c;特别关注向量数据库的重要性和在实际项目中的应用。 通过深入剖析腾讯云向量数据库及其在金融信用数据库分析中的实战运用&#xff0c;为读者提供全面而实用的指南&#xff0c;帮助他们理解、…...

2015年五一杯数学建模A题不确定性条件下的最优路径问题解题全过程文档及程序

2015年五一杯数学建模 A题 不确定性条件下的最优路径问题 原题再现 目前&#xff0c;交通拥挤和事故正越来越严重的困扰着城市交通。随着我国交通运输事业的迅速发展&#xff0c;交通“拥塞”已经成为很多城市的“痼疾”。在复杂的交通环境下&#xff0c;如何寻找一条可靠、快…...

5、Qt:项目中包含多个子项目(.pro)/子模块(.pri)

一、说明&#xff1a; 在进行项目开发过程中&#xff0c;会涉及子项目/子模块的问题 Qt中使用TEMPLATE subdirs添加多个子项目&#xff1b;子项目可以单独编译生成可执行文件&#xff08;exe&#xff09;或者动态链接库&#xff08;dll&#xff09;等&#xff0c;供其他模块…...

Facebook的特点优势

Facebook作为全球最大的社交媒体平台之一&#xff0c;同时也是最受欢迎的社交网站之一&#xff0c;Facebook具有许多独特的特点和优势。本文小编将说一些关于Facebook的特点及优势。 1、全球化 Facebook拥有数十亿的全球用户&#xff0c;覆盖了几乎所有国家和地区。这使得人们…...

Spring框架体系及Spring IOC思想

目录 Spring简介Spring体系结构SpringIOC控制反转思想自定义对象容器Spring实现IOCSpring容器类型容器接口容器实现类对象的创建方式使用构造方法使用工厂类的方法使用工厂类的静态方法对象的创建策略对象的销毁时机生命周期方法获取Bean对象的方式通过id/name获取通过类型获取…...

WT588F02B-8S语音芯片:16位DSP技术引领个性化功能产品新时代

随着科技的快速发展&#xff0c;语音芯片作为人机交互的核心组件&#xff0c;在各个领域的应用越来越广泛。唯创知音推出的WT588F02B-8S语音芯片&#xff0c;以其强大的16位DSP技术和丰富的内置资源&#xff0c;正成为行业内的翘楚。 首先&#xff0c;唯创知音WT588F02B-8S是一…...

数字逻辑电路基础-时序逻辑电路之移位寄存器

文章目录 一、移位寄存器定义二、verilog源码三、仿真结果 一、移位寄存器定义 移位寄存器定义 A shift register is a type of digital circuit using a cascade of flip flops where the output of one flip-flop is connected to the input of the next. 移位寄存器是一种将…...

DEM分析

一、实验名称&#xff1a; DEM分析 二、实验目的&#xff1a; 通过本实验练习&#xff0c;掌握DEM的建立与应用基本方法。 三、实验内容和要求&#xff1a; 实验内容&#xff1a; 利用ARCGIS软件相关分析工具及实验数据&#xff0c;创建DEM&#xff0c;并计算相应坡度的区…...

全面探讨HTTP协议从0.9到3.0版本的发展和特点

前言&#xff1a; 最近的几场面试都问到了http的相关知识点&#xff0c;博主在此结合书籍和网上资料做下总结。本篇文章讲收录到秋招专题&#xff0c;该专栏比较适合刚入坑Java的小白以及准备秋招的大佬阅读。 如果文章有什么需要改进的地方欢迎大佬提出&#xff0c;对大佬有帮…...

中通快递查询入口,根据物流更新量筛选出需要的单号记录

批量中通快递单号的物流信息&#xff0c;根据物流更新量将需要的单号记录筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 中通快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;并登录 步骤2&#xff1a;点击主…...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

SpringCloudGateway 自定义局部过滤器

场景&#xff1a; 将所有请求转化为同一路径请求&#xff08;方便穿网配置&#xff09;在请求头内标识原来路径&#xff0c;然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL

ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...