【C++/STL】:哈希 -- 线性探测哈希桶

目录

  • 💡前言
  • 一,unordered系列容器
  • 二,哈希
    • 2.1 哈希的概念
    • 2.2 哈希函数
    • 2.3 哈希冲突
  • 三,哈希冲突解决(重点)
    • 3.1 开放定址法
    • 3.2 哈希桶(重点)
  • 四,线性探测的实现
    • 4.1 线性探测的基本框架
    • 4.2 插入操作
    • 4.3 查找操作
    • 4.4 删除操作
  • 五,哈希桶的实现(重点)
    • 5.1 哈希桶的基本框架
    • 5.2 插入操作
    • 5.3 查找操作
    • 5.4 删除操作
  • 六,优化思考

点击跳转至文章: 【C++/STL】:set和map的介绍及基本使用

💡前言

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2 N log2N,即最差情况下需要比较红黑树的高度次。

在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,unordered系列的底层是哈希表

本篇文章的内容是学习unordered系列的容器,重点学习什么是哈希,知道什么是哈希冲突,并且掌握解决哈希冲突的两种常用方法:线性探测&哈希桶。

一,unordered系列容器

unordered系列容器有4个:
(1) unordered_map
(2) unordered_set
(3) unordered_multimap
(4) unordered_multiset

它们与 map/set 系列容器的核心功能的重叠度90%,使用方法基本类似,这里不再重复演示。最主要的区别是:遍历unordered系列容器中存储的数据是无序的,并且就性能而言,unordered系列容器在插入,查找,删除方面时间复杂度是 O(1)因此在平时,我们更加推荐使用unordered系列容器

二,哈希

2.1 哈希的概念

构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与元素之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

2.2 哈希函数

这里介绍两种常见的哈希函数:直接定址法除留余数法

(1) 直接定址法:用 key 值直接在哈希表中映射一个绝对位置或相对位置
优点:快,无冲突
缺点:要事先知道 key 值的分步情况,并且适用于范围集中的数据

(2) 除留余数法:用 key 值模(%)表的大小 N,利用得到的余数(0 ~ N-1)把 key 值映射到表中的位置

这种方法适用任何类型的数据(一些非整形类型可以通过某种方法转换),但是这也会导致一个不可避免的问题:产生哈希冲突

2.3 哈希冲突

不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

如果用上面的除留余数法来通俗的解释,就是不同的 key 值,取模后映射到了相同的位置

三,哈希冲突解决(重点)

解决哈希冲突两种常见的方法是:开放地址法哈希桶

3.1 开放定址法

当发生哈希冲突时(自己的位置被占了),如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把 key 值存放到冲突位置中的"下一个" 空位置中去

那如何寻找下一个空位置呢?
(1) 线性探测:从发生冲突的位置开始,按 +1,+2,+3……依次向后探测,直到寻找到下一个空位置为止

比如下表中现在需要插入元素44,先通过哈希函数计算出映射位置为4,但是4位置被占,所以要从这个位置开始,依次向后探测,找到8空位置。

在这里插入图片描述

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

(2) 二次探测:从发生冲突的位置开始,按 +1 ^ 2 , + 2 ^ 2,+3 ^ 2……依次向后探测,直到寻找到下一个空位置为止。

3.2 哈希桶(重点)

哈希桶也叫拉链法,首先对 key 值用哈希函数计算出位置,具有相同位置的 key 值归于同一集合,每一个集合称为一个桶各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中

unordered系列的底层哈希表解决哈希冲突的结构就是哈希桶

从下图可以看出,哈希表中每个桶中放的都是发生哈希冲突的元素

在这里插入图片描述

四,线性探测的实现

4.1 线性探测的基本框架

哈希表中的每一个位置给一个状态标记。这样可以避免值存在但是由于冲突位置删除后置空导致查找不到的问题

//枚举三种状态
enum State
{EMPTY,  //空EXIST,  //存在元素DELETE  //元素删除
};template<class K, class V>
struct HashData
{pair<K, V> _kv;State _state = EMPTY;
};template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
HashTable()
{_tables.resize(10); //初始化10个空间
}//插入,查找,删除等功能……private:vector<HashData<K, V>> _tables;size_t _n = 0;  //表中的数据个数
};

4.2 插入操作

(1) 计算出映射下标,这里用除留余数法要除以数据的个数size,而不是容量的大小capacity,因为要用下标访问,如果除以容量用下标访问会越界。
(2) 插入时发生哈希冲突,用线性探测往后找空位置。
(3) 扩容负载因子:表中的元素个数 / 表的长度 ,一般控制在0.7~0.8
(4) 去重操作,插入时可以复用Find进行判断,值存在,插入失败。

bool Insert(const pair<K, V>& kv)
{//去冗余,值存在,插入失败if (Find(kv.first))return false;// 扩容//负载因子:表中的元素个数 / 表的长度 一般控制在0.7~0.8if (_n * 10 / _tables.size() >= 7){HashTable<K, V, Hash> newHT;//不要在原表扩容,会破坏映射关系//_tables.resize(_tables.size() * 2); //err//重开一个新的哈希表newHT._tables.resize(_tables.size() * 2);//遍历旧表,复用Insert,(可避免重复写映射下标的逻辑)// 把旧表中存在的数据插入新表for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST)newHT.Insert(_tables[i]._kv);}//交换_tables.swap(newHT._tables);}//计算出映射下标//这里用除留余数法。要除以数据的个数size,而不是容量的大小capacity//因为要用下标访问,如果除以容量用下标访问会越界Hash hs;size_t hashi = hs(kv.first) % _tables.size();//哈希冲突,线性探测继续找下一个位置while (_tables[hashi]._state == EXIST){hashi++;hashi %= _tables.size(); //解决回绕}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;_n++;return true;
}

4.3 查找操作

算出映射下标确定查找起点,再继续往后找,值相等且为存在状态就找到了,避免删除后还能找到

HashData<K, V>* Find(const K& key)
{Hash hs;size_t hashi = hs(key) % _tables.size();while (_tables[hashi]._state != EMPTY){//值相等且为存在状态,避免删除后还能找到if (_tables[hashi]._kv.first == key&& _tables[hashi]._state == EXIST){return &_tables[hashi];}hashi++;hashi %= _tables.size(); //解决回绕}return nullptr;
}

4.4 删除操作

复用Find,值存在就删除

bool Erase(const K& key)
{HashData<K, V>* ret = Find(key);if (ret == nullptr)return false;else{ret->_state = DELETE;_n--;return true;}
}

五,哈希桶的实现(重点)

5.1 哈希桶的基本框架

哈希桶的结构通俗的说就是在顺序表中挂一个个链表,所以这里的顺序表是一个指针数组

在这里插入图片描述

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 HashBucket
{typedef HashNode<K, V> Node;
public:HashBucket(){_tables.resize(10, nullptr); //初始化10个空间}//插入,删除,查找等功能……private:vector<Node*> _tables;  // 指针数组size_t _n = 0;          // 表中储存的数据个数
};

5.2 插入操作

(1) 核心操作就是链表的头插

(2) 扩容:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容

在这里插入图片描述

bool Insert(const pair<K, V>& kv)
{//去冗余,值存在,插入失败if (Find(kv.first))return false;//负载因子==1,扩容//方式1//if(_n == _tables.size())//{//	HashBucket<K, V> newHB;//	newHB._tables.resize(_tables.size() * 2);//	//遍历旧表,复用Insert,(可避免重复写映射下标的逻辑)//	// 把旧表中存在的数据插入新表//	for (size_t i = 0; i < _tables.size(); i++)//	{//		Node* cur = _tables[i];//		if (cur)//		{//			newHB.Insert(cur->_kv);//			cur = cur->_next;//		}//	}//	_tables.swap(newHB._tables);//}//方式1的扩容逻辑消耗的空间太多,假设有一万个节点,就要重新new一万个//又要销毁一万个,效率不高//方式2// 是在扩容后也重新利用旧表中的旧节点,把旧节点挪动到新表中Hash hs;if (_n == _tables.size()){vector<Node*> newtables(_tables.size() * 2, nullptr);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;//旧节点挪到新表的下标映射size_t hashi = hs(cur->_kv.first) % newtables.size();//头插到新表cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}size_t hashi = hs(kv.first) % _tables.size(); //计算映射下标//头插Node* newnode = new Node(kv);newnode->_next = _tables[hashi];_tables[hashi] = newnode;_n++;return true;
}

5.3 查找操作

核心操作就是链表的查找

Node* Find(const K& key)
{Hash hs;size_t hashi = hs(key) % _tables.size(); //计算映射下标Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;
}

5.4 删除操作

核心操作就是链表的删除
在这里插入图片描述

bool Erase(const K& key)
{Hash hs;size_t hashi = hs(key) % _tables.size(); //计算映射下标Node* cur = _tables[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;_n--;return true;}//没找到,继续向下走prev = cur;cur = cur->_next;}return false;
}

六,优化思考

只能存储key为整形的元素,其他类型怎么解决?

需要支持转整形的仿函数:
(1) 当key为浮点数,负数,指针等可以直接强转为无符号整形的类型
(2) 当key为 string 类(频繁使用)

//当key为浮点数,负数,指针等时,强转成无符号整形,再进行映射
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){size_t n = 0;for (auto& ch : key){n *= 31; //尽量减少冲突n += ch;}return n;}
};

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

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

相关文章

Let‘s Encrypt

启动Https,需要从证书授权机构(简称CA)处获取一个证书,Lets Encrypt就是一个CA Lets Encrypt上可以获得免费的ssl证书,时间是3个越 下面通过Nginx和Lets Encrypt让网站升级到HTTPS Certbot简介 Certbot是Lets Encrypt官方推荐的获取证书的客户端 Centos 7.9操作 yum install …

openai版本不适配问题(在windows系统下openai migrate)

问题如下&#xff1a; 方法&#xff1a; 参考官网v1.0.0 Migration Guide openai/openai-python Discussion #742 GitHub 具体步骤&#xff1a; 1、curl -fsSL https://docs.grit.io/install | bash -x 但该命令可能不好用————将‘curl -fsSL https://docs.grit.io/i…

谷歌AI拿下IMO奥数银牌,数学推理模型AlphaProof面世,强化学习 is so back

用上了 Gemini 大模型与 AlphaZero 强化学习算法,几何、代数、数论全都会。 对于 AI 来说,奥数不再是问题了。 本周四,谷歌 DeepMind 的人工智能完成了一项壮举:用 AI 做出了今年国际数学奥林匹克竞赛 IMO 的真题,并且距拿金牌仅一步之遥。 上周刚刚结束的 IMO 竞赛共有…

深入分析 Android ContentProvider (九)

文章目录 深入分析 Android ContentProvider (九)ContentProvider 的高级使用及最佳实践&#xff08;续&#xff09;1. 复杂查询与联合查询复杂查询示例 2. 数据同步与一致性示例&#xff1a;使用事务确保数据一致性 3. 数据分页加载示例&#xff1a;分页加载数据 4. 内容提供者…

【CN】Argo 持续集成和交付(二)

7.25.通知 概述 Argo CD 通知持续监控 Argo CD 应用程序&#xff0c;并提供一种灵活的方式来通知用户应用程序状态的重要变化。使用灵活的触发器和模板机制&#xff0c;可以配置何时发送通知以及通知内容。Argo CD 通知包含有用的触发器和模板目录。因此&#xff0c;可以直接…

pycharm连接mysql

1、按照下图在pycharm找到数据库设置 在PyCharm右侧工具栏有Database&#xff0c;点击打开如果没有&#xff0c;则在view | Tool Windows | Database 选择显示 2、按照下图所示位置找到mysql&#xff08;本机由于配置过&#xff0c;所以由recent&#xff0c;第一次配置在列表中…

Cybersecurity ASPICE实施策略-基于ISO/SAE 21434-亚远景科技

近几年&#xff0c;随着软件定义汽车和汽车的智能化和网联化&#xff0c;使得汽车融合了现代通信与网络通信技术&#xff0c;实现了车与人、车与车、车与道路、车与云端等智能信息交互和共享&#xff0c;也让车具备了环境感知、协同控制、智能决策等功能&#xff1b;与此同时&a…

苹果推送iOS 18.1带来Apple Intelligence预览

&#x1f989; AI新闻 &#x1f680; 苹果推送iOS 18.1带来Apple Intelligence预览 摘要&#xff1a;苹果向iPhone和iPad用户推送iOS 18.1和iPadOS 18.1开发者预览版Beta更新&#xff0c;带来“Apple Intelligence”预览。目前仅支持M1芯片或更高版本的设备。Apple Intellige…

使用 Elasticsearch 和 LlamaIndex 保护 RAG 中的敏感信息和 PII 信息

作者&#xff1a;来自 Elastic Srikanth Manvi 在这篇文章中&#xff0c;我们将研究在 RAG&#xff08;检索增强生成&#xff09;流程中使用公共 LLMs 时保护个人身份信息 (personal identifiable information - PII) 和敏感数据的方法。我们将探索使用开源库和正则表达式屏蔽 …

正余弦算法作者又提出新算法!徒步优化算法(HOA)-2024年一区顶刊新算法-公式原理详解与性能测评 Matlab代码免费获取

声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 原理简介 算法伪代码 性能测评 参考文献 …

基于vue-onlyoffice实现企业office web在线应用

目录 1.背景... 1 2.Onlyoffice介绍... 2 3.Onlyoffice核心api介绍... 2 3.1 ApiDocument 2 3.2 ApiParagraph. 2 3.3 ApiTable. 2 3.4. ApiRange. 3 4.Onlyoffice插件介绍... 3 4.1 插件定义... 3 4.2 插件对象... 3 4.3 插件结构... 4 4.4 插件内嵌使用方式... 4…

搜索引擎项目(四)

SearchEngine 王宇璇/submit - 码云 - 开源中国 (gitee.com) 基于Servlet完成前后端交互 WebServlet("/searcher") public class DocSearcherServlet extends HttpServlet {private static DocSearcher docSearcher new DocSearcher();private ObjectMapper obje…

Luma AI发布文生视频大模型Dream Machine——可免费在线试玩

Sora模型的文生视频能力&#xff0c;想必一定惊艳过你。虽然Sora模型很惊艳&#xff0c;但是并没有开放给普通大众。Luma AI发布文生视频大模型Dream Machine模型&#xff0c;可以免费供大家使用&#xff0c;任何人只要到Luma AI的官方网站&#xff0c;就可体验Luma AI的文生视…

六个开源的PDF转Markdown项目

✨ 1: gptpdf gptpdf 是一个利用VLLM解析PDF为Markdown的工具&#xff0c;几乎完美支持数学公式、表格等。 GPTPDF 是一个使用视觉大模型&#xff08;如 GPT-4o&#xff09;将 PDF 文件解析成 Markdown 文件的工具。它主要用于高效地解析 PDF 文档中的排版、数学公式、表格、…

React Native新架构系列-新架构介绍

从今天起&#xff0c;会陆续更新React Native新架构相关的系列内容&#xff0c;本系列基于React Native 0.73.4版本&#xff0c;从一名Android开发者的视角进行介绍。本系列介绍的内容默认读者对React Native有一定的了解&#xff0c;对基础的开发内容不再赘述。 前言 首先介绍…

【优选算法】——leetcode——438.找到字符串中所有字母异位词

目录 1.题目 2.题目理解 3.算法原理 1.如何快速判断两个字符串是否是异位词 2.解决问题 暴力求解——>滑动窗口哈希表 滑动窗口 利用滑动窗口哈希表解决问题 优化&#xff1a;更新结果的判断条件 4.编程代码 C代码 1.频率统计 2. 双指针 C语言代码 1.字符频率…

【qt小系统】传感器云平台3D散点图(附源码)

摘要&#xff1a;本文主要使用QT5&#xff0c;实现了一个传感器云平台的小示例&#xff0c;模拟的是各类传感器的添加&#xff0c;例如&#xff1a;热成像传感器、温度传感器、超声波传感器&#xff0c;模拟添加完成后&#xff0c;会自动将此传感器的三维坐标增加到3D散点图上&…

Vmware安装openstack

安装虚拟机 创建完成后&#xff0c;点击开启虚拟机 稍等执行成功后 上传压缩包到指定目录。将yoga_patch.tar.gz包上传至/root目录下&#xff0c;将stack3_without_data.tar.gz包使用WinSCP上传至/opt目录下 vim run_yoga.sh #/bin/bash cd /root sudo apt-get update tar -xzv…

「问题解决」jdk高版本导致请求返回对象转换报错

报错&#xff1a;Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected native java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException accessible: module java.base does not “opens java.lang” to unn…

UnityShaderUI编辑器扩展

前言&#xff1a; 当我们在制作通用Shader的时候&#xff0c;避免不了许多参数混杂在一起&#xff0c;尽管在材质面板已经使用过Header标签来区分&#xff0c;但是较长的Shader参数就会导致冗余&#xff0c;功能块不够简约明了&#xff0c;如图&#xff1a; 对于Shader制作者来…