哈希表与散列表的原理及C++实现

1. 什么是哈希表?

哈希表(Hash Table)是一种高效的数据结构,用于存储键值对(Key-Value Pairs)。它通过哈希函数(Hash Function)将键(Key)映射到一个固定大小的数组(称为散列表)中的某个位置,从而实现快速的插入、查找和删除操作。

哈希表的核心思想是通过哈希函数将键转换为数组的索引,从而直接访问对应的值。理想情况下,哈希表的插入、查找和删除操作的时间复杂度都是 O(1)


2. 哈希表的核心概念

2.1 哈希函数(Hash Function)

哈希函数是哈希表的核心,它负责将任意大小的键映射到一个固定范围的整数(通常是数组的索引)。一个好的哈希函数应满足以下条件:

  1. 均匀分布:哈希值应尽可能均匀地分布在数组中,以减少冲突。
  2. 高效计算:哈希函数的计算应尽可能快。
  3. 一致性:相同的键应始终映射到相同的哈希值。

2.2 散列表(Hash Table)

散列表是一个数组,用于存储键值对。哈希函数将键映射到数组的某个索引,然后将值存储在该索引对应的位置。

2.3 冲突(Collision)

由于哈希函数的输出范围有限,而键的范围可能很大,因此不同的键可能会映射到相同的索引,这种现象称为冲突。常见的冲突解决方法包括:

  1. 链地址法(Chaining):将冲突的键值对存储在同一个索引位置的链表中。
  2. 开放地址法(Open Addressing):通过探测方法(如线性探测、二次探测)在散列表中寻找下一个可用的位置。

3. 哈希表的实现

下面我们使用 链地址法 来实现一个简单的哈希表,并用 C++ 代码演示其操作。

3.1 C++ 实现

#include <iostream>
#include <list>
#include <vector>
#include <utility> // for std::pairclass HashTable {
private:static const int TABLE_SIZE = 10; // 散列表的大小std::vector<std::list<std::pair<int, std::string>>> table; // 使用链表解决冲突// 哈希函数int hashFunction(int key) {return key % TABLE_SIZE;}public:HashTable() : table(TABLE_SIZE) {}// 插入键值对void insert(int key, const std::string& value) {int index = hashFunction(key);for (auto& pair : table[index]) {if (pair.first == key) {pair.second = value; // 如果键已存在,更新值return;}}table[index].emplace_back(key, value); // 否则插入新的键值对}// 查找键对应的值std::string search(int key) {int index = hashFunction(key);for (const auto& pair : table[index]) {if (pair.first == key) {return pair.second; // 找到键,返回对应的值}}return "Not Found"; // 未找到键}// 删除键值对void remove(int key) {int index = hashFunction(key);auto& chain = table[index];for (auto it = chain.begin(); it != chain.end(); ++it) {if (it->first == key) {chain.erase(it); // 删除键值对return;}}}// 打印散列表void printTable() const {for (int i = 0; i < TABLE_SIZE; ++i) {std::cout << "Bucket " << i << ": ";for (const auto& pair : table[i]) {std::cout << "(" << pair.first << ", " << pair.second << ") ";}std::cout << std::endl;}}
};int main() {HashTable hashTable;// 插入键值对hashTable.insert(1, "Alice");hashTable.insert(2, "Bob");hashTable.insert(11, "Charlie"); // 11 和 1 会发生冲突hashTable.insert(12, "David");   // 12 和 2 会发生冲突// 打印散列表std::cout << "Hash Table Contents:" << std::endl;hashTable.printTable();// 查找键std::cout << "Search for key 11: " << hashTable.search(11) << std::endl;std::cout << "Search for key 3: " << hashTable.search(3) << std::endl;// 删除键hashTable.remove(11);std::cout << "After removing key 11:" << std::endl;hashTable.printTable();return 0;
}

运行结果:

Hash Table Contents:
Bucket 0: 
Bucket 1: (1, Alice) (11, Charlie) 
Bucket 2: (2, Bob) (12, David) 
Bucket 3: 
Bucket 4: 
Bucket 5: 
Bucket 6: 
Bucket 7: 
Bucket 8: 
Bucket 9: 
Search for key 11: Charlie
Search for key 3: Not Found
After removing key 11:
Bucket 0: 
Bucket 1: (1, Alice) 
Bucket 2: (2, Bob) (12, David) 
Bucket 3: 
Bucket 4: 
Bucket 5: 
Bucket 6: 
Bucket 7: 
Bucket 8: 
Bucket 9: 

3.2 代码解析

  1. 哈希函数

    • 使用简单的取模运算 key % TABLE_SIZE 将键映射到散列表的索引。
  2. 冲突解决

    • 使用链地址法,每个索引位置存储一个链表,链表中存放所有映射到该索引的键值对。
  3. 插入操作

    • 计算键的哈希值,找到对应的索引。
    • 如果键已存在,更新其值;否则将新的键值对插入链表。
  4. 查找操作

    • 计算键的哈希值,遍历对应索引位置的链表,查找键是否存在。
  5. 删除操作

    • 计算键的哈希值,遍历对应索引位置的链表,删除匹配的键值对。
  6. 打印散列表

    • 遍历散列表,打印每个索引位置的链表内容。

4. 哈希表的性能分析

  • 时间复杂度

    • 插入、查找和删除的平均时间复杂度为 O(1)
    • 在最坏情况下(所有键都映射到同一个索引),时间复杂度退化为 O(n)
  • 空间复杂度

    • 空间复杂度为 O(n),其中 n 是键值对的数量。

5. 哈希表的应用场景

  • 字典:存储键值对,支持快速查找。
  • 缓存:如 LRU 缓存,利用哈希表实现快速访问。
  • 数据库索引:哈希表可用于实现数据库的索引结构。
  • 去重:利用哈希表快速判断元素是否已存在。

6. 总结

哈希表是一种高效的数据结构,通过哈希函数将键映射到数组索引,从而实现快速的插入、查找和删除操作。链地址法是解决冲突的常见方法之一,适合处理哈希冲突的情况。

7.练习

ACWing模拟散列表
这里我们展示一种不使用STL的链表写法。使用STL可以使用mapunordered_map等快速解决,或者像上文中提到的vector模拟链表。这里我们使用的是一种链式结构记录。数据结构书本上链表需要不断malloc,在算法比赛当中是不合适的,因为会消耗大量的时间。这种链式结构的原理如下:

1. 插入操作 (insert 函数)
  1. 计算哈希值:通过 (num % mod + mod) % mod 计算键 num 的哈希值 pos
  2. 更新 headhead 自增,表示新节点的索引。
  3. 链式插入
    • 将新节点的 nxt 指向当前 h[pos](即链表的头部)。
    • 更新 h[pos]head,表示新节点成为链表的头部。
    • e[head] 赋值为 num,存储键值。
  4. 结束:插入操作完成。

假设现在状态如此,我们来模拟插入新来的一个编号为8的节点的过程。
在这里插入图片描述

  • 首先计算8号节点的位置,假设经过哈希之后,需要放在5号桶。
  • 接着,根据代码的操作,先将8号节点的nxt[8] 指向当前5号桶的头部节点7,并更新头部为5号桶头部节点为8号节点,再把值存入e数组的一个空位置。
  • 完成操作后,链表的形式如下图所示
    在这里插入图片描述
2. 查询操作 (query 函数)
  1. 计算哈希值:通过 (num % mod + mod) % mod 计算键 num 的哈希值 pos
  2. 初始化遍历:从 h[pos] 开始遍历链表。
  3. 遍历链表
    • 如果当前节点的值 e[i] 等于 num,返回 true
    • 否则,移动到下一个节点 i = nxt[i]
  4. 遍历结束:如果遍历完链表仍未找到 num,返回 false

代码逻辑总结

  • h[pos]:存储哈希值为 pos 的链表的头节点索引。
  • nxt[i]:存储索引为 i 的节点的下一个节点索引。
  • e[i]:存储索引为 i 的节点的值。
  • head:表示当前可用的节点索引,每次插入时自增。

通过链式结构,哈希表可以高效地处理冲突,并支持动态插入和查询操作。


具体代码实现如下

#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 1e5 + 7;
const int mod = 1e5 + 7;
int h[maxn], e[maxn], nxt[maxn], head = 0;
void insert(int num){int pos = (num % mod + mod) % mod;nxt[++head] = h[pos];h[pos] = head;e[head] = num;
}
bool query(int num){int pos = (num % mod + mod) % mod;for(int i = h[pos];i!=-1;i=nxt[i]){if(e[i] == num)return true;}return false;
}
int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);memset(h, -1, sizeof(h));int n;cin>>n;for(int i=1;i<=n;i++){string op;int num;//scanf("%s %d", op, &num);cin>>op>>num;if(op == "I"){if(!query(num))insert(num);}else{if(query(num))printf("Yes\n");elseprintf("No\n");}}return 0;
}

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

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

相关文章

图像分类与目标检测算法

在计算机视觉领域&#xff0c;图像分类与目标检测是两项至关重要的技术。它们通过对图像进行深入解析和理解&#xff0c;为各种应用场景提供了强大的支持。本文将详细介绍这两项技术的算法原理、技术进展以及当前的落地应用。 一、图像分类算法 图像分类是指将输入的图像划分为…

数字化转型:概念性名词浅谈(第四讲)

​大家好&#xff0c;本篇文章是在新年之际写的&#xff0c;所以在这里先给大家拜个年。 今天要介绍的名词为ETL: ETL&#xff0c;是英文Extract-Transform-Load的缩写&#xff0c;用来描述将数据从来源端经过抽取&#xff08;extract&#xff09;、转换&#xff08;transfor…

UVM factory机制

目录 1. factory-register 1.1 uvm_object_registry#(type T=uvm_object, string Tname="") 1.1 uvm_default_factory::register 2. factory-override 2.1 set_type_override(uvm_object_wrapper override_type) 2.2 set_inst_override(uvm_object_wrapper ove…

奥迪改名风波再起,A6L能否率队创下新奇迹

文/王俣祺 导语&#xff1a;春节假期刚过&#xff0c;奥迪的车型命名规则又变了。在如今以内卷为主基调的环境下&#xff0c;车型改名可不是小事&#xff0c;而奥迪的这次调整背后藏着许多深意&#xff0c;也预示着2025年奥迪在产品布局上的新动向。 改名能否“改命” 回溯到…

改进Transformer,解读Tokenformer论文:基于参数分词化重新思考Transformer的扩展策略

Transformer 训练成本高昂的问题日益凸显&#xff0c;不仅需要耗费巨额的资金与大量的计算资源&#xff0c;还对环境产生了不可忽视的影响&#xff0c;最近由北京大学与谷歌联合发表的一篇论文&#xff0c;为这一棘手难题带来了全新的曙光。论文中提出的创新方案&#xff0c;有…

【STM32】HAL库USB虚拟U盘MSC配置及采用自带的Flash作为文件系统

【STM32】HAL库USB虚拟U盘MSC实现配置及采用自带的Flash作为文件系统 本文将自带的Flash作为文件系统 通过配置USB的MSC功能实现虚拟U盘 没有单独建立FATFS文件系统 仅仅是配置USB和Flash读写而已 当然 这里也可以用外部Flash等等 也可以配置文件系统来进行套壳 但总体而言不如…

Nginx通过设置自定义标记识别代理调用

Nginx通过设置自定义标记识别代理调用 业务场景 最近遇到一个业务场景&#xff0c;部署在云端服务器的一个平台&#xff0c;接口提供给多个现场调用&#xff0c;其中一个现场是通过nginx代理服务器代理转发到云服务器&#xff0c;另外一个现场则是直接通过云服务器接口进行调…

【DeepSeek系列】01 DeepSeek-V1 快速入门

1、DeepSeek简介 2024年底&#xff0c;DeepSeek 相继推出了其第一代推理大模型&#xff1a;DeepSeek-R1-Zero 和 DeepSeek-R1。 DeepSeek-R1-Zero 是一个通过大规模强化学习&#xff08;RL&#xff09;训练的模型&#xff0c;训练过程中没有使用监督微调&#xff08;SFT&…

基于LabVIEW的Modbus-RTU设备通信失败问题分析与解决

在使用 LabVIEW 通过 Modbus-RTU 协议与工业设备进行通信时&#xff0c;可能遇到无法正常发送或接收指令的问题。常见原因包括协议参数配置错误、硬件连接问题、数据帧格式不正确等。本文以某 RGBW 控制器调光失败为例&#xff0c;提出了一种通用的排查思路&#xff0c;帮助开发…

密云生活的初体验

【】在《岁末随笔之碎碎念》里&#xff0c;我通告了自己搬新家的事情。乙巳年开始&#xff0c;我慢慢与大家分享自己买房装修以及在新家的居住体验等情况。 跳过买房装修的内容&#xff0c;今天先说说这三个月的生活体验。 【白河】 潮白河是海河水系五大河之一&#xff0c;贯穿…

Python爬虫:1药城店铺爬虫(完整代码)

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

openwebui入门

1 简介 ‌Open WebUI‌&#xff08;网址是openwebui.com&#xff09;是一个高度可扩展、功能强大且用户友好的自托管Web用户界面&#xff0c;专为完全离线操作设计&#xff0c;编程语言是python。它支持对接Ollama和OpenAI兼容的API的大模型。‌ Open WebUI‌在架构上是一种中…

Day36-【13003】短文,数组的行主序方式,矩阵的压缩存储,对称、三角、稀疏矩阵和三元组线性表,广义表求长度、深度、表头、表尾等

文章目录 本次课程内容第四章 数组、广义表和串第一节 数组及广义表数组的基本操作数组的顺序存储方式-借用矩阵行列式概念二维数组C语言对应的函数-通常行主序方式 矩阵的压缩存储对称矩阵和三角矩阵压缩存储后&#xff0c;采用不同的映射函数稀疏矩阵-可以构成三元组线性表三…

3-Not_only_base/2018网鼎杯

3-Not_only_base 打开code MCJIJSGKPZZYXZXRMUW3YZG3ZZG3HQHCUS 分析&#xff1a; 首先看题知道解密过程中肯定有base解密。 知识点1&#xff1a; Base64字符集&#xff1a; 包含大小写字母&#xff08;A-Z、a-z&#xff09;、数字&#xff08;0-9&#xff09;以及两个特殊字…

deepseek、qwen等多种模型本地化部署

想要在本地部署deepseek、qwen等模型其实很简单,快跟着小编一起部署吧 1 环境搭建 1.1下载安装环境 首先我们需要搭建一个环境ollama,下载地址如下 :Ollama 点击Download 根据自己电脑的系统选择对应版本下载即可 1.2 安装环境(window为例) 可以直接点击安装包进行安…

02/06 软件设计模式

目录 一.创建型模式 抽象工厂 Abstract Factory 构建器 Builder 工厂方法 Factory Method 原型 Prototype 单例模式 Singleton 二.结构型模式 适配器模式 Adapter 桥接模式 Bridge 组合模式 Composite 装饰者模式 Decorator 外观模式 Facade 享元模式 Flyw…

Idea ⽆ Maven 选项

Idea ⽆ Maven 选项 1. 在 Idea 项⽬上右键2. 选中 Maven 选项 如果在创建 Spring/Spring Boot 项⽬时&#xff0c;Idea 右侧没有 Maven 选项&#xff0c;如下图所示&#xff1a; 此时可以使⽤以下⽅式解决。 1. 在 Idea 项⽬上右键 2. 选中 Maven 选项 选中 Maven 之后&#…

用Deepseek做EXCLE文件对比

背景是我想对比两个PO系统里的一个消息映射&#xff0c;EDI接口的mapping有多复杂懂的都懂&#xff0c;它还不支持跨系统版本对比&#xff0c;所以我费半天劲装NWDS&#xff0c;导出MM到excle&#xff0c;然后问题来了&#xff0c;我需要对比两个excel文件里的内容&#xff0c;…

OpenCV:图像轮廓

目录 简述 1. 什么是图像轮廓&#xff1f; 2. 查找图像轮廓 2.1 接口定义 2.2 参数说明 2.3 代码示例 2.4 运行结果 3. 绘制图像轮廓 3.1 接口定义 3.2 参数说明 3.3 代码示例 3.4 运行结果 4. 计算轮廓周长 5. 计算轮廓面积 6. 示例&#xff1a;计算图像轮廓的面…

在Mac mini M4上部署DeepSeek R1本地大模型

在Mac mini M4上部署DeepSeek R1本地大模型 安装ollama 本地部署&#xff0c;我们可以通过Ollama来进行安装 Ollama 官方版&#xff1a;【点击前往】 Web UI 控制端【点击安装】 如何在MacOS上更换Ollama的模型位置 默认安装时&#xff0c;OLLAMA_MODELS 位置在"~/.o…