c++哈希表(原理、实现、开放寻址法)适合新手

c++系列哈希的原理及实现(上)

文章目录

  • c++系列哈希的原理及实现(上)
  • 前言
  • 一、哈希的概念
  • 二、哈希冲突
  • 三、哈希冲突解决
    • 3.1、开放寻址法
    • 3.2、删除操作
    • 3.3、负载因子
    • 四、代码实现
  • 总结


前言

红黑树平衡树和哈希有不同的用途。

红黑树、平衡树这类数据结构是有序的数据结构,它们可以高效地进行范围查询,比如查找一个区间内的值。在需要保持数据有序存储,并且频繁进行插入、删除和查找操作的场景下很有用,像数据库索引的实现就可能会用到。

而哈希主要用于快速的数据查找。它通过一个哈希函数把数据映射到一个特定的位置,理想情况下,查找操作可以在常数时间复杂度内完成,也就是时间复杂度为O(1)。在只需要快速判断某个元素是否存在的场景下,哈希就非常合适。所以学了红黑树等平衡树之后还需要学哈希,是因为它们解决的是不同类型的问题。


一、哈希的概念

首先我们要知道顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。
而我们想要的是可以不经过任何比较,一次直接从表中得到要搜索的元素。
下面我们看一个小问题:

有这样一个由小写字母组成的字符串“abcdef”我们需要查找某一个字符是否存在,这时我们就可以将他映射到一个数组中,要查找字符是否存在,我们只需要利用这给映射方法,判断它对应数组中的值是否为1即可。

string s = "abcdef";
int arr[26] = {0};//开辟空间初始化为0
for (auto ch : s)
{arr[ch - 'a'] = 1;
}

这就是利用哈希思想,哈希就是一种特殊的存储结构,通过特定的函数(方法),使得数据的存储位置与它的关键码之间建立一种一一映射的关系,这样在查找数据时就可以直接通过关键值来快速查找,这个函数也称为映射函数。
再来看一个例子:
对于这样一组数据{1,2,4,5,7,6},我们要将他映射到一个数组中就可以使用直接映射,即:
在这里插入图片描述
而如果数据的范围较大如:{1,2,3,4,7,6,9999999},这样一组数据,如果使用直接映射,就要开辟足够大的空间,这样开空间浪费的有点离谱了,这时我们就需要给它提供一个方法,将数据控制在一定的范围,所以就有了,除留余数法。我们将这个方法封装为一个函数,这个函数就称为映射函数
除留余数法:
我们将待存入数,对开辟空间大小,进行取余,得到的余数,作为下标,利用下标将带存入数存入到空间中。(待存入数据,我们称为关键字,余数我们称为,存储位置)

size_t hashFunc(size_t key)
{size_t i=key%capacity;return i;
}

在这里插入图片描述
下面我们再思考一个问题:
如果我们想再存储一个3,通过3进行哈希映射发现,家被偷了,该怎么办呢?
我们需要的位置已经存在值,这种问题称为,哈希冲突。我们先来对上面进行一下总结再来解决这个问题。

总结:

映射关系
1、直接定址法(直接映射):适合数据范围小,数据量小,没有重复值的数据。不存在哈希冲突。
2、除留余数法:适合数据范围大,数据量可以大。存在哈希冲突。

二、哈希冲突

哈希冲突指的是在使用哈希表进行数据存储和查找时,不同的关键字通过哈希函数计算得到了相同的哈希值(存储位置)。
哈希函数是将关键字映射到哈希表中的某个位置的函数。由于哈希表的存储空间是有限的,而可能的关键字数量是无限的,所以不同的关键字有可能被映射到相同的位置,这就产生了哈希冲突。
哈希冲突会影响哈希表的性能,比如增加查找、插入等操作的时间复杂度。

解决哈希冲突有两种常见的方法:

  • 闭散列:也叫开放寻址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。
  • 开散列:也叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

这次我们先介绍闭散列法

三、哈希冲突解决

3.1、开放寻址法

开放定址法是解决哈希冲突的一种方法,其基本思想是当发生冲突时,通过某种系统的方法在哈希表中寻找下一个空槽位,并将冲突的关键码存储在这个槽位中。常用的方法有两种:

1、线性探测:当发生哈希冲突时,从映射位置开始,向后按顺序查找,直到找到下一个空位。
2、二次探测:从映射位置开始,依次增加1、4、9…,探测距离是i^2的倍数,i为从零开始自增的整数。

线性探测例子:
如果我们想将11插入哈希表中,通过哈希函数得到的存储位置已经被占用,我们就从当前位置开始,依次向后查找空位置。
在这里插入图片描述
这种解决哈希冲突的方式,又给我们带来了一个麻烦。
大家思考一下,当我们要查找1这个元素是否存在哈希表中,我们该如何来查找呢?解铃还须系铃人,肯定第一时间就想到利用哈希映射,找到这个值的存储位置,然后比较哈希表中的值,是否等于要查找的值。这个问题是很简单的,那么我们又该如何查找11呢?显然简单的使用哈希函数映射得到到值是无法找到的,这时我们就需要判断它后面是否还有值,如果有,我们就将他与后面存储值继续比较,直到找到,或者遇到值为空的位置。

3.2、删除操作

接着上面的来讲,如果我们要将2删除,是否可以直接将它所在位置,制为空。如果我们这样做了那么上面查找11的操作,该怎么来完成呢?这时我们就需要一个方式将存储位置的状态进行标记,我们将删除位置标记为删除、已有数据位置,标记为存在,未存储数据位置设置为空。这样我们在查找元素时,跳过删除位置,直至空或找到终止程序。

3.3、负载因子

在我们向哈希表中不断映射数据时,发生哈希冲突的概率会随数据量的增大而提高,这会导致我们插入、查找效率大幅度下降,这时我们就需要对哈希表进行扩容操作。那么什么时候扩容呢,总不能等到哈希表存满,哈希冲突最多的时候再进行扩容吧!!!!这时就有了负载因子,来作为我们判断是否扩容的阈值,这个负载因子是使用已插入元素除以哈希表大小。那么当这个负载因子多大时我们对哈希表进行扩容操作呢?这个没有具体要求,但是我们要知道,如果设置太小,会产生空间浪费,设置太大就会发生较多的哈希冲突,所以我们也不能设置的太离谱,在接下来的代码实现中,我将他设置为0.7。

四、代码实现

具体操作及原理,上面已经讲解过了,由于这个结构实现起来还是很简单的,大家在看代码时结合注释及上文。

//使用枚举标识哈希表状态
enum status {EMPTY,EXIST,DELETE
};
template<class k, class v>
struct hashdate {pair<k, v>_kv;status _s;
};
//使用模板
template<class k>//仿函数将待存入数据统一处理为无符号整型
struct HashFunc {size_t operator ()(const k& key){return (size_t)key;}
};
template<>//模板特化,应字符串存储问题
struct HashFunc<string>
{size_t operator()(const string& key){size_t cout = 0;for (int i = 0; i < key.size(); i++){cout += key[i];}return cout;}
};
//缺省值为哈希函数(仿函数),可跟据需要自己提供
template<class k, class v,class Hash=HashFunc<k>>
class hashTable {Hash hash;
public:hashTable(){_table.resize(10);}bool Insert(const pair<k, v>& kv){if (Find(kv)){return false;}//负载因子设置为0.1因为隐式这里因为会发生隐式类型转换,特殊处理一下if (_n * 10 / _table.size() == 7){size_t newsize = _table.size() * 2;hashTable<k, v> Newhs;Newhs._table.resize(newsize);for (int i = 0; i < _table.size(); i++){if (_table[i]._s == EXIST){Newhs.Insert(_table[i]._kv);}}_table.swap(Newhs._table);}size_t hashi = hash(kv.first) % _table.size();while (_table[hashi]._s == EXIST){hashi++;hashi %= _table.size();}_table[hashi]._kv = kv;_table[hashi]._s = EXIST;_n++;return true;}//查找函数hashdate<k,v>* Find(const pair<k, v>& kv) {size_t hsi = hash(kv.first) % _table.size();while (_table[hsi]._s != EMPTY){if (_table[hsi]._s == EXIST && _table[hsi]._kv.first == kv.first){return &_table[hsi];}hsi++;}return NULL;}bool earse(const k& key){size_t hasi = key % _table.size();while (_table[hasi]._s != EMPTY){if (_table[hasi]._s == EXIST && _table[hasi]._kv.first == key){_table[hasi]._s = DELETE;return true;}hasi++;}return false;}void print(){for (int i = 0; i < _table.size(); i++){if (_table[i]._s == EXIST){cout << _table[i]._kv.first << ' ';}}}
private:size_t _n = 0;vector<hashdate<k, v>> _table;//使用vector充当哈希表
};

总结

线性探测优点:实现非常简单
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。如何缓解呢?这就需要用到开放定址法了

下篇也完成了,链接放下面了

c++哈希开散列讲解

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

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

相关文章

了解HTTPS以及CA在其中的作用

在这个信息爆炸的时代&#xff0c;每一次指尖轻触屏幕&#xff0c;都是一次数据的旅行。但您是否真正了解&#xff0c;这些数据在通往目的地的旅途中&#xff0c;是如何被保护的呢&#xff1f; HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09;是一种安全的网…

Flink学习连载文章8--时间语义

Time的分类 (时间语义) EventTime:事件(数据)时间,是事件/数据真真正正发生时/产生时的时间 IngestionTime:摄入时间,是事件/数据到达流处理系统的时间 ProcessingTime:处理时间,是事件/数据被处理/计算时的系统的时间 EventTime的重要性 假设&#xff0c;你正在去往地下停…

sizeof和strlen区分,(好多例子)

sizeof算字节大小 带\0 strlen算字符串长度 \0之前

Simulink的SIL软件在环测试

以基于模型的设计&#xff08;MBD&#xff09;的软件开发时&#xff0c;需要进行SIL&#xff08;软件在环测试&#xff09;。SIL测试就是在PC上验证模型是否与代码功能一致。在项目开展中&#xff0c;用在需要将控制器生成移植到硬件前&#xff0c;把控制器的模块生成代码&…

浅谈js中onmouseleave和onmouseout的区别

同步发布于我的网站 &#x1f680; 背景介绍基本概念区别详解 无子元素的情况有子元素的情况 实际应用场景 使用 onmouseleave使用 onmouseout 注意事项总结 背景介绍 在前端开发中&#xff0c;我们经常需要为元素绑定鼠标事件&#xff0c;以实现各种交互效果。onmouseleave…

【Git系列】利用 Bash 脚本获取 Git 最后一次非合并提交的提交人

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

36 基于单片机的电磁炉系统设计

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;通过DS18B20温度传感器检测温度&#xff0c;通过八位数码管显示&#xff0c; 如果温度超过阈值&#xff0c;则蜂鸣器报警&#xff0c;红灯亮起&#xff1b;若不超过阈值&…

『 Linux 』数据链路层 - ARP协议及数据链路层周边问题

文章目录 ARP协议ARP欺骗RARP协议 DNS服务ICMP协议ping 命令正向代理服务器反向代理服务器 ARP协议 博客『 Linux 』数据链路层 - MAC帧/以太网帧中提到,当数据需要再数据链路层进行无网络传输时需要封装为MAC帧,而MAC帧的报文结构如下: 帧头部分存在两个字段分别为 “目的地址…

MySQL(数据库)

1.数据库? 数据库是管理数据(增删改查CRUD)的软件 MySQL(开源&免费) 是一个数据库软件 (客户端-服务器)结构的软件 客户端服务器通过网络进行通信 客户端(Client):主动发起请求的一方,客户端给服务器发起的数据称为请求(request) 服务器(Server):被动接受请求的一方,…

vue3----API

组合式API 1.setup 定义的数据和方法必须return出去才能够被使用 不使用this,this指向了undefined <script> export default {setup () {console.log(setup)const message this is messageconst logmessage ()>{console.log(message)}return {message,logmessag…

服务器遭受DDoS攻击后如何恢复运行?

当服务器遭受 DDoS&#xff08;分布式拒绝服务&#xff09;攻击 后&#xff0c;恢复运行需要快速采取应急措施来缓解攻击影响&#xff0c;并在恢复后加强防护以减少未来攻击的风险。以下是详细的分步指南&#xff1a; 一、应急处理步骤 1. 确认服务器是否正在遭受 DDoS 攻击 …

【maven-5】Maven 项目构建的生命周期:深入理解与应用

1. 生命周期是什么 ​在Maven出现之前&#xff0c;项目构建的生命周期就已经存在&#xff0c;软件开发人员每天都在对项目进行清理&#xff0c;编译&#xff0c;测试及部署。虽然大家都在不停地做构建工作&#xff0c;但公司和公司间&#xff0c;项目和项目间&#xff0c;往往…

【机器学习】CatBoost 模型实践:回归与分类的全流程解析

一. 引言 本篇博客首发于掘金 https://juejin.cn/post/7441027173430018067。 PS&#xff1a;转载自己的文章也算原创吧。 在机器学习领域&#xff0c;CatBoost 是一款强大的梯度提升框架&#xff0c;特别适合处理带有类别特征的数据。本篇博客以脱敏后的保险数据集为例&#x…

lua download

https://www.lua.org/ https://www.lua.org/versions.html#5.4

基于大数据python 房屋价格数据分析预测可视化系统(源码+LW+部署讲解+数据库+ppt)

&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 很对人不知道选题怎么选 不清楚自己适合做哪块内容 都可以免费来问我 避免后期給自己答辩找麻烦 增加难度&#xff08;部分学校只有一次答辩机会 没弄好就延迟…

pikachu文件上传漏洞通关详解

声明&#xff1a;文章只是起演示作用&#xff0c;所有涉及的网站和内容&#xff0c;仅供大家学习交流&#xff0c;如有任何违法行为&#xff0c;均和本人无关&#xff0c;切勿触碰法律底线 目录 概念&#xff1a;什么是文件上传漏洞一、客户端check二、MIME type三、getimagesi…

Monitor 显示器软件开发设计入门二

基础篇--显示驱动方案输出接口介绍 写在前面&#xff1a;首先申明&#xff0c;这篇文章是写给那些初入显示器软件行业的入门者&#xff0c;或是对显示器没有基本知识的小白人员。如您是行业大咖大神&#xff0c;可以绕行&#xff0c;可看后期进阶文章。 上篇介绍了输入接口及相…

像素流送api ue多人访问需要什么显卡服务器

关于像素流送UE推流&#xff0c;在之前的文章里其实小芹和大家聊过很多&#xff0c;不过今天偶然搜索发现还是有很多小伙伴&#xff0c;在搜索像素流送相关的问题&#xff0c;搜索引擎给的提示有这些。当然这些都是比较短的词汇&#xff0c;可能每个人真正遇到的问题和想获取的…

【21-30期】Java技术深度剖析:从分库分表到微服务的核心问题解析

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Java &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 文章题目&#xff1a;Java技术深度剖析&#xff1a;从分库分表到微服务的核心问题解析 摘要&#xff1a; 本…

Flutter 权限申请

这篇文章是基于permission_handler 10.2.0版本写的 前言 在App开发过程中我们经常要用到各种权限&#xff0c;我是用的是permission_handler包来实现权限控制的。 pub地址&#xff1a;https://pub.dev/packages/permission_handler permission_handler 权限列表 变量 Androi…