【C++高阶六】哈希与哈希表

【C++高阶六】哈希与哈希表

  • 1.什么是哈希?
  • 2.unordered系列容器
  • 3.哈希表
    • 3.1将key与存储位置建立映射关系
      • 3.1.1直接定址法
      • 3.1.2除留余数法(产生哈希冲突)
    • 3.2解决哈希冲突的方法
      • 3.2.1闭散列(开放定址法)
      • 3.3.2开散列(链地址法)(哈希桶)
  • 4.实现哈希表(除留余数法建立映射)
    • 4.1闭散列实现
      • 4.1.1状态与结构
      • 4.1.2 Insert
      • 4.1.3负载因子和扩容
      • 4.1.4 Find
      • 4.1.5 Erase
      • 4.1.6 完整代码
    • 4.2 开散列实现
      • 4.2.1 状态与结构
      • 4.2.2 Insert
      • 4.2.3 负载因子和扩容
      • 4.2.4 Find
      • 4.2.5 Erase
      • 4.2.6 仿函数
      • 4.2.7 完整代码

1.什么是哈希?

理想中的搜索方法:不经过任何比较和遍历就可以直接从表中搜索想要的元素
如果构造一种存储结构,并通过某种函数能使元素的存储位置与它自身关键码之间建立映射的关系,那么在查找时只要通过函数就可以快速找到该元素

当向该结构中插入元素时,根据待插入元素的关键码,以函数计算出该元素的存储位置并按此位置进行存储
当向该结构中搜索元素时,用函数对元素的关键码进行同样的计算求得元素的存储位置,在结构中按此位置取元素比较,若关键码相等则搜索成功
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

2.unordered系列容器

unordered_mapunordered_setmapset是什么关系?
在这里插入图片描述
在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序

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

3.哈希表

3.1将key与存储位置建立映射关系

3.1.1直接定址法

在这里插入图片描述

每一个值都有一个唯一位置,适用于范围比较集中的数据

3.1.2除留余数法(产生哈希冲突)

在这里插入图片描述

范围不集中,分布分散
key值跟存储位置的关系是取模出来的,不同的值有可能映射到相同的位置,造成哈希冲突,如:5和55取模后都为5

3.2解决哈希冲突的方法

3.2.1闭散列(开放定址法)

当发生哈希冲突时,如果哈希表未被装满,说明哈希表中必然还有空位置,则可以用线性探测把key存放到冲突位置中的下一个位置,若有两个取模相同的值,则将先进来的占住当前取模位置,后进来的向后探测,若有空位置则放入
在这里插入图片描述

key%len,len是表的长度

3.3.2开散列(链地址法)(哈希桶)

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

在这里插入图片描述

4.实现哈希表(除留余数法建立映射)

4.1闭散列实现

4.1.1状态与结构

整体实现全部放入名为开放地址法Open_Address的命名空间中
在这里插入图片描述

假设要删除55,因为55取余后为5,所以先去位置为5的地方去找,没有找到则继续向后寻找,到空才结束
但是把33删除后,再次查找13时,由于提前遇到空,则查找直接结束,不会向后寻找 所以找到后不能直接删除,会影响继续查找

设置三种状态: 空、存在、删除

enum Status//状态
{EMPTY,//空EXIST,//存在DELETE//删除
};template<class K,class V>
struct HashData
{pair<K, V> _kv;//kv值Status _status = EMPTY;//状态默认为空
};

state默认设为空,不然有可能造成映射位置没有数据,但状态为存在的情况

4.1.2 Insert

key值跟存储位置的关系是取模出来的(key%len
len为 _tables.size() 还是 _tables.capacity()

假设为capacity,若当前位置为空,将值填入并将状态设置为存在,就会造成越界,在vector中operator[]会做越界检查,下标是否小于size

插入:

bool Insert(const pair<K, V>& kv)
{size_t hashi = kv.first % _table.size();_table[hashi]._kv = kv;_table[hashi]._status = EXIST;
}

线性探测:

while (_table[hashi]._status == EXIST)
{hashi++;hashi %= _table.size();//防止越界,可以回到0
}

完整代码:

bool Insert(const pair<K, V>& kv)
{size_t hashi = kv.first % _table.size();while (_table[hashi]._status == EXIST){hashi++;hashi %= _table.size();//防止越界,可以回到0}_table[hashi]._kv = kv;_table[hashi]._status = EXIST;
}

4.1.3负载因子和扩容

哈希表冲突越多效率就越低,若表中位置都满了就需要扩容,所以提出负载因子的概念
负载因子 = 填入表的元素个数 / 表的长度,用于表示 表储存数量的百分比
填入表的元素个数 越大,表示冲突的可能性越大,所以在开放定址法时,应该控制在0.7-0.8以下,超过就会扩容

if (_num * 10 / _table.size() == 7)
{size_t new_num = _table.size() * 2;HashTable<K, V> new_HT;for (size_t i = 0; i < _table.size();i++)//遍历旧表{if (_table[i]._status == EXIST){new_HT.Insert(_table[i]._kv);}}_table.swap(new_HT._table);
}

创建new_HT,将其中的_tables的size进行扩容,通过复用insert的方式,完成对新表的映射,最后交换旧表的_tables与new_HT的_tables ,以达到更新数据的目的

4.1.4 Find

若当前位置的状态为存在或者删除,则继续找, 遇见空就结束
若在循环中找到了,则返回对应位置的地址,若没找到则返回nullptr
删除只是把要删除的数据的状态改为DELETE,但是数据本身还是在的,所以Find还是可以找到的

HashData<K, V>* Find(const K& key)
{size_t hashi = key % _table.size();while (_table[hashi]._status != EMPTY){if (_table[hashi]._status == EXIST && _table[hashi]._kv.first == key){return &_table[hashi];}hashi++;hashi %= _table.size();}return nullptr;
}

4.1.5 Erase

bool Erase(const k& key)
{HashData<K, V>* temp = Find(key);if (temp){temp->_status = DELETE;_num--;return true;}else{return false;}
}

4.1.6 完整代码

using namespace std;
namespace Open_Address//闭散列(开放定址法)
{enum Status//状态{EMPTY,//空EXIST,//存在DELETE//删除};template<class K,class V>struct HashData{pair<K, V> _kv;//kv值Status _status = EMPTY;//状态默认为空};template<class K,class V>class HashTable{public:HashTable(){_table.resize(10);}bool Insert(const pair<K, V>& kv){//负载因子等于0.7时扩容if (_num * 10 / _table.size() == 7){size_t new_num = _table.size() * 2;HashTable<K, V> new_HT;for (size_t i = 0; i < _table.size();i++)//遍历旧表{if (_table[i]._status == EXIST){new_HT.Insert(_table[i]._kv);}}_table.swap(new_HT._table);}//线性探测size_t hashi = kv.first % _table.size();while (_table[hashi]._status == EXIST){hashi++;hashi %= _table.size();//防止越界,可以回到0}_table[hashi]._kv = kv;_table[hashi]._status = EXIST;}HashData<K, V>* Find(const K& key){size_t hashi = key % _table.size();while (_table[hashi]._status != EMPTY){if (_table[hashi]._status == EXIST && _table[hashi]._kv.first == key){return &_table[hashi];}hashi++;hashi %= _table.size();}return nullptr;}bool Erase(const k& key){HashData<K, V>* temp = Find(key);if (temp){temp->_status = DELETE;_num--;return true;}else{return false;}}private:vector<HashData<K, V>> _table;//指针数组size_t _num;//存储的数据个数};
}

4.2 开散列实现

4.2.1 状态与结构

整体实现全部放入名为哈希桶Hash_Bucket的命名空间中

template<class K,class V>
struct HashNode
{HashNode(const pair<K,V>& kv):_kv(kv),_next(nullptr){}HashNode<K, V>* _next;//指向下一个节点pair<K, V> _kv;//记录数据
};

4.2.2 Insert

在同一个桶中数据不分先后
在这里插入图片描述
创建一个新节点newnode,使newnode的next连接到当前映射位置,再让newnode成为桶的头节点

size_t hashi = kv.first % _table.size();
Node* newnode = new Node(kv);
newnode->_next = _table[hashi];
_table[hashi] = newnode;

4.2.3 负载因子和扩容

负载因子越大,冲突的概率越高,查找效率越低,空间利用率越高
负载因子越小,冲突的概率越低,查找效率越高,空间利用率越低

在这里插入图片描述

在这里插入图片描述
原表的节点重新计算位置后移动到新表中
由于新表的size大小为20,所以12可以找到对应位置的桶 ,而102没有对应大小的桶,所以取模来到对应2位置处,与2形成链式结构
遍历旧表中的数据,若数据为空,就往后遍历
若数据不为空,则将其移动到新表中 ,进行头插

if (_num == _table.size())
{size_t newsize = _table.size() == 0 ? 10 : _table.size()*2;vector<Node*> new_table;new_table.resize(newsize, nullptr);for (size_t i = 0; i < _table.size(); i++){Node* temp = _table[i];while (temp){Node* next = temp->_next;//挪动到新表size_t hashi = _table[i] % new_table.size();temp->_next = new_table[i];new_table[hashi] = temp;temp = next; }_table[i] = nullptr;}_table.swap(new_table);
}

4.2.4 Find

使用temp记录当前映射位置,遍历当前位置的单链表 ,查询是否有key值的存在,若有则返回temp,若没有则返回空

Node* Find(const K& key)
{if (_table.size() == 0){return nullptr;}size_t i = key % _table.size();Node* temp = _table[i];while (temp){if (temp->_kv.first == key){return temp;}temp = temp->_next;}return nullptr;
}

4.2.5 Erase

在这里插入图片描述
假设要删除3,则 需让表的位置指向要删除节点的下一个
在这里插入图片描述
假设要删除13,则需找到删除节点的前一个节点,使其指向要删除节点达到后一个节点

bool Erase(const K& key)
{size_t i = key % _table.size();Node* prev = nullptr;//记录前一个结点Node* temp = _table[i];while (temp){if (temp->_kv.first == key){if(prev == nullptr)//删除头节点{_table[i] = temp->_next;}else{prev->_next = temp->_next;}delete temp;return true;}prev = temp;temp = temp->_next;}return false;
}

4.2.6 仿函数

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

若为string类型,使用insert无法计算对应的hashi值,所以需要加入仿函数
在这里插入图片描述
加入全局仿函数:

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 hash = 0;for (auto e : key){hash *= 31;//乘31或者131都行,为了避免数字重复hash += e;}return hash;}
};

再次使用 HashTable时不用传入仿函数也能调用string 类型

4.2.7 完整代码

namespace Hash_Bucket//开散列(哈希桶)
{template<class K,class V>struct HashNode{HashNode(const pair<K,V>& kv):_kv(kv),_next(nullptr){}HashNode<K, V>* _next;//指向下一个节点pair<K, V> _kv;//记录数据};template<class K,class V,class Hash = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:bool Insert(const pair<K,V>& kv){//负载因子等于1时扩容Hash hash;if (_num == _table.size()){size_t newsize = _table.size() == 0 ? 10 : _table.size()*2;vector<Node*> new_table;new_table.resize(newsize, nullptr);for (size_t i = 0; i < _table.size(); i++){Node* temp = _table[i];while (temp){Node* next = temp->_next;//挪动到新表size_t hashi = hash(temp->_kv.first) % new_table.size();temp->_next = new_table[i];new_table[hashi] = temp;temp = next; }_table[i] = nullptr;}_table.swap(new_table);}size_t hashi = hash(kv.first) % _table.size();Node* newnode = new Node(kv);//头插newnode->_next = _table[hashi];_table[hashi] = newnode;_num++;return true;}Node* Find(const K& key){if (_table.size() == 0){return nullptr;}size_t i = hash(key) % _table.size();Node* temp = _table[i];while (temp){if (temp->_kv.first == key){return temp;}temp = temp->_next;}return nullptr;}bool Erase(const K& key){size_t i = key % _table.size();Node* prev = nullptr;//记录前一个结点Node* temp = _table[i];while (temp){if (temp->_kv.first == key){if(prev == nullptr)//删除头节点{_table[i] = temp->_next;}else{prev->_next = temp->_next;}delete temp;return true;}prev = temp;temp = temp->_next;}return false;}private:vector<Node*> _table;//指针数组size_t _num = 0;//存储的数据个数};
}

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

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

相关文章

Vue 3 +Ant Design Vue 父容器样式不影响子级,隔离

公共样式文件 common.scss.zz-ant-status-bar {div {font-size: 12px;padding: 0 8px;} }页面代码<div class"zz-ant-status-bar"><a-row><a-col :span"6" ><a-progress :percent"progress.percent" size"small"…

k8s 简介及部署方法以及各方面应用

Kubernetes 简介及部署方法Kubernetes&#xff08;简称 K8s&#xff09;是一个开源的容器编排平台&#xff0c;用于自动化容器化应用的部署、扩展、管理和运维。它由 Google 基于内部的 Borg 系统经验开发&#xff0c;2014 年开源后由云原生计算基金会&#xff08;CNCF&#xf…

Class A 包含字段 x Class B 也包含字段 x,如果判断List<A> lista 和 List<B> listb 有相同的 x?

要判断两个不同类型的对象列表 List<A> 和 List<B> 是否包含相同的 x字段值&#xff08;即两个列表中至少有一个 x是相同的&#xff09;&#xff0c;你可以使用 Java 8 的 Stream API 来实现。import java.util.List; import java.util.Set; import java.util.stre…

SpringBoot整合Camunda工作流

什么是工作流&#xff1f;概述 工作流是将一组任务组织起来以完成某个经营过程&#xff1a;定义了任务的触发顺序和触发条件&#xff0c;每个任务可以由一个或多个软件系统完成&#xff0c;也可以由一个或一组人完成&#xff0c;还可以由一个或多个人与软件系统协作完成&#x…

2025年09月计算机二级Java选择题每日一练——第四期

计算机二级中选择题是非常重要的&#xff0c;所以开始写一个每日一题的专栏。 答案及解析将在末尾公布&#xff01; 今日主题&#xff1a;面向对象特性 1、有两个类 A 和 B 的定义如下&#xff1a; class A{final int x10;public void show(){System.out.print(x " &quo…

《Nature》新文解读:电化学辅助核聚变的实验验证与机制分析

前言一篇于2025年8月发表在《Nature》期刊上的重磅研究&#xff0c;由加拿大不列颠哥伦比亚大学&#xff08;UBC&#xff09;Curtis P. Berlinguette教授领导的跨学科团队完成&#xff0c;首次在实验上证实&#xff1a;通过电化学方法向钯金属靶中加载氘&#xff0c;可显著提升…

【基础-判断】用户在长视频、短视频、直播、通话、会议、拍摄类应用等场景下,可以采用悬停适配在折叠屏半折态时,上屏进行浏览下屏进行交互操作

用户在长视频、短视频、直播、通话、会议、拍摄类应用等场景下,可以采用悬停适配在折叠屏半折态时,上屏进行浏览下屏进行交互操作。 解释如下: ✅ 1. 悬停态适配机制的核心设计 HarmonyOS 针对折叠屏半折态(悬停态)提供了分屏交互框架,其核心逻辑是: 上屏(Upper Scre…

nodejs安装后 使用npm 只能在cmd 里使用 ,但是不能在poowershell使用,只能用npm.cmd

nodejs安装后 使用npm 只能在cmd 里使用 &#xff0c;但是不能在poowershell使用&#xff0c;只能用npm.cmdnodejs版本&#xff1a;22.18.0 刚安装好nodejs&#xff0c;在 PowerShell 中无法执行 npm&#xff0c;但能执行npm.cmd&#xff0c;这通常是因为 PowerShell 的执行策略…

【链表 - LeetCode】2. 两数相加

谁都逃不掉 LeetCode &#xff01;&#xff01;哈哈哈~~~ 开刷&#xff1a;&#xff09; 2025年08月22日 题目&#xff1a;2. 两数相加 - 力扣&#xff08;LeetCode&#xff09; 知识点&#xff1a;链表 /*** Definition for singly-linked list.* struct ListNode {* in…

WG-Tools 在线开发者工具推荐:完全免费、无广告干扰、无需安装、即开即用

WG-Tools 在线开发者工具箱全面探秘: 一站式效率提升平台前言一. WG-Tools 平台介绍 &#x1f6e0;️平台概览技术架构亮点二. 功能模块详细介绍 &#x1f3af;&#x1f4dd; 文本处理工具 (Text Tools)1. JSON工具2. XML工具3. 文本对比4. 正则表达式工具5. Markdown编辑器6. …

四十二、【核心功能强化】用例管理与调试:批量删除与在线请求测试

四十二、【核心功能强化】用例管理与调试:批量删除与在线请求测试 前言 准备工作 第一部分:后端实现 1. 修改 `TestCaseViewSet` (`api/views.py`) 2. 后端 API 权限: 第二部分:前端实现 1. 更新 `api/testcase.ts` API 服务 2. 改造 `TestCaseListView.vue` (用例列表页面…

从H.264到AV1:音视频技术演进与模块化SDK架构全解析

引言 过去二十年&#xff0c;音视频技术经历了从 文件点播 → 流媒体 → 实时直播 → 互动协作 的深刻演变。早期的视频更多停留在娱乐与媒体分发层面&#xff0c;而如今&#xff0c;它已经成为数字化社会的“实时交互基座”。从 安防监控的秒级告警、工业巡检的远程操作&…

Kubernetes 调度器 详解

1. 调度器在 K8s 中的位置与核心流程API Server ←→ etcd ←→ kube-scheduler ←→ kubelet创建&#xff1a;用户提交 Pod 描述&#xff08;YAML/Helm/Operator&#xff09;。监听&#xff1a;调度器通过 Watch 机制捕获到 spec.nodeName"" 的 Pod。过滤&#xff1…

51.Seata-TCC模式

前面两种XA模式和TA模式,都是用了加锁。 TCC模式则不会加锁,性能更好。 TCC模式跟AT模式非常相似, 1.AT模式下,第一阶段直接提交事务。 2.TCC模式下,第一阶段不是提交事务,而是资源的预留冻结。 不同的是二阶段TCC通过人工编码来实现数据恢复。 需要实现三个方法 …

什么是数据分类分级?数据分类分级技术实现路径及产品推荐

什么是数据分类分级&#xff1f; 数据分类分级是指按照一定的原则、方法和标准&#xff0c;对数据进行系统化的类别划分和级别确定。具体而言&#xff0c;数据分类是依据数据的属性、特征、来源、用途等维度&#xff0c;将数据划分为不同的类别&#xff0c;如按照业务领域可分为…

深度学习——神经网络

在当今人工智能蓬勃发展的时代&#xff0c;深度学习和神经网络已经成为最受关注的技术领域之一。从智能手机的人脸识别到自动驾驶汽车的环境感知&#xff0c;从医疗影像分析到金融风险预测&#xff0c;这些技术正在深刻改变我们的生活和工作方式。本文将带您了解深度学习和神经…

uniapp image标签展示视频第一帧

?vframe/jpg/offset/1/ 加到视频后面获取第一帧图片 ?vframe/jpg/offset/1/w/400/h/300 设置宽高 ?imageView2/0/w/2000/interlace/1 设置图片分辨率 2000 // 后面的 /1/ 是第几帧 <image class"thumb" :src"videoUrl?vframe/jpg/offset/1/" mode…

前端本地模糊搜索1.0 按照匹配位置加权

需求背景 公司项目为Saas ERP系统&#xff0c;客户需要快速开单需要避免接口带来的延迟问题。所以需要将商品数据保存在本地。所以本地搜索 权重 这一套组合拳需要前端自己实现。 搜索示例 示例1&#xff1a;输入&#xff1a;"男士真皮钱包"进行模糊匹配优先匹配完全…

Linux学习-网络编程2

1.tcp可能出现粘包解决&#xff1a;要让消息之间有边界1.结束标志 \r\n2.固定长度3.协议结构体2.recv和sendrecv原型&#xff1a;ssize_t recv(int sockfd, void *buf, size_t len, int flags); 功能&#xff1a;从sockfd接收信息 参数&#xff1a;sockfd&#xff1a;要…

【普通地质学】构造运动与地质构造

名词解释走向&#xff1a;倾斜的层面与水平面的交线走向线&#xff0c;走向线两端延伸的方向即为走向&#xff1b;构造运动&#xff1a;由于地球内部动力引起的组成岩石圈物质的机械运动&#xff0c;也可称地壳运动或岩石圈运动&#xff1b;按方向分为垂直运动和水平运动&#…