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

【数据结构与算法】第30篇:哈希表(Hash Table)

一、什么是哈希表1.1 基本思想哈希表通过哈希函数将关键字映射到数组的某个位置实现快速访问。textkey → 哈希函数 → 数组下标 → 访问/存储示例hash(key) key % 10key25 → 25%105 → 存入下标5key37 → 37%107 → 存入下标71.2 哈希冲突不同的key映射到同一个位置称为哈希冲突。textkey25 → 5 key35 → 5 // 冲突解决冲突的两种主要方法链地址法每个位置是一个链表开放地址法冲突后找下一个空位二、哈希函数2.1 常用哈希函数方法公式适用场景直接定址法H(key)a×keyb关键字分布连续除留余数法H(key)key % p最常用p为质数平方取中法取平方后中间几位关键字无规律折叠法分割后求和关键字位数多2.2 除留余数法cint hash(int key, int size) { return key % size; // size通常取质数 }为什么size取质数减少冲突概率使分布更均匀。三、链地址法拉链法3.1 原理每个数组元素是一个链表的头指针冲突的元素放入同一链表。text数组: [0] → [1] → [2] → [3] → ... 链表: ↓ key1 → key2 → ...3.2 代码实现c#include stdio.h #include stdlib.h #define SIZE 10 // 链表节点 typedef struct Node { int key; struct Node *next; } Node; // 哈希表 typedef struct { Node *buckets[SIZE]; } HashTable; // 初始化 void initHashTable(HashTable *ht) { for (int i 0; i SIZE; i) { ht-buckets[i] NULL; } } // 哈希函数 int hash(int key) { return key % SIZE; } // 插入 void insert(HashTable *ht, int key) { int index hash(key); Node *newNode (Node*)malloc(sizeof(Node)); newNode-key key; newNode-next ht-buckets[index]; ht-buckets[index] newNode; } // 查找 int search(HashTable *ht, int key) { int index hash(key); Node *cur ht-buckets[index]; while (cur ! NULL) { if (cur-key key) return 1; cur cur-next; } return 0; } // 删除 int delete(HashTable *ht, int key) { int index hash(key); Node *cur ht-buckets[index]; Node *prev NULL; while (cur ! NULL) { if (cur-key key) { if (prev NULL) { ht-buckets[index] cur-next; } else { prev-next cur-next; } free(cur); return 1; } prev cur; cur cur-next; } return 0; } // 打印 void printHashTable(HashTable *ht) { for (int i 0; i SIZE; i) { printf([%d]: , i); Node *cur ht-buckets[i]; while (cur ! NULL) { printf(%d - , cur-key); cur cur-next; } printf(NULL\n); } } int main() { HashTable ht; initHashTable(ht); insert(ht, 25); insert(ht, 35); insert(ht, 45); insert(ht, 17); insert(ht, 28); printf(哈希表链地址法:\n); printHashTable(ht); printf(\n查找25: %s\n, search(ht, 25) ? 找到 : 未找到); printf(查找99: %s\n, search(ht, 99) ? 找到 : 未找到); delete(ht, 35); printf(\n删除35后:\n); printHashTable(ht); return 0; }运行结果text哈希表链地址法: [0]: NULL [1]: NULL [2]: NULL [3]: NULL [4]: NULL [5]: 45 - 35 - 25 - NULL [6]: NULL [7]: 17 - NULL [8]: 28 - NULL [9]: NULL 查找25: 找到 查找99: 未找到 删除35后: [5]: 45 - 25 - NULL四、开放地址法4.1 线性探测冲突时依次检查下一个位置H (H(key) i) % SIZE缺点容易产生聚集一连串被占用的位置。c#define SIZE 10 #define EMPTY -1 typedef struct { int data[SIZE]; } OpenHashTable; void initOpenHash(OpenHashTable *ht) { for (int i 0; i SIZE; i) { ht-data[i] EMPTY; } } int hash(int key) { return key % SIZE; } // 线性探测插入 void linearInsert(OpenHashTable *ht, int key) { int index hash(key); int i 0; while (ht-data[(index i) % SIZE] ! EMPTY i SIZE) { i; } if (i SIZE) { ht-data[(index i) % SIZE] key; } else { printf(哈希表已满\n); } } // 线性探测查找 int linearSearch(OpenHashTable *ht, int key) { int index hash(key); int i 0; while (i SIZE) { int pos (index i) % SIZE; if (ht-data[pos] key) return pos; if (ht-data[pos] EMPTY) return -1; // 空位说明不存在 i; } return -1; }4.2 二次探测解决线性探测的聚集问题H (H(key) i²) % SIZEc// 二次探测插入 void quadraticInsert(OpenHashTable *ht, int key) { int index hash(key); int i 0; while (ht-data[(index i * i) % SIZE] ! EMPTY i SIZE) { i; } if (i SIZE) { ht-data[(index i * i) % SIZE] key; } else { printf(哈希表已满\n); } }五、完整哈希表实现链地址法动态扩容c#include stdio.h #include stdlib.h #define INIT_SIZE 8 #define LOAD_FACTOR 0.75 typedef struct Node { int key; struct Node *next; } Node; typedef struct { Node **buckets; int size; // 桶的数量 int count; // 元素个数 } HashTable; // 哈希函数 int hash(HashTable *ht, int key) { return key % ht-size; } // 创建哈希表 HashTable* createHashTable() { HashTable *ht (HashTable*)malloc(sizeof(HashTable)); ht-size INIT_SIZE; ht-count 0; ht-buckets (Node**)calloc(ht-size, sizeof(Node*)); return ht; } // 插入不检查扩容 void insertNoResize(HashTable *ht, int key) { int index hash(ht, key); Node *newNode (Node*)malloc(sizeof(Node)); newNode-key key; newNode-next ht-buckets[index]; ht-buckets[index] newNode; ht-count; } // 查找 int search(HashTable *ht, int key) { int index hash(ht, key); Node *cur ht-buckets[index]; while (cur ! NULL) { if (cur-key key) return 1; cur cur-next; } return 0; } // 获取所有键 void getAllKeys(HashTable *ht, int *keys, int *len) { *len 0; for (int i 0; i ht-size; i) { Node *cur ht-buckets[i]; while (cur ! NULL) { keys[(*len)] cur-key; cur cur-next; } } } // 扩容 void resize(HashTable *ht) { int oldSize ht-size; Node **oldBuckets ht-buckets; // 创建新哈希表 ht-size oldSize * 2; ht-count 0; ht-buckets (Node**)calloc(ht-size, sizeof(Node*)); // 重新插入所有元素 for (int i 0; i oldSize; i) { Node *cur oldBuckets[i]; while (cur ! NULL) { insertNoResize(ht, cur-key); Node *temp cur; cur cur-next; free(temp); } } free(oldBuckets); } // 插入带扩容 void insert(HashTable *ht, int key) { if ((float)ht-count / ht-size LOAD_FACTOR) { printf(负载因子 %.2f %.2f扩容到 %d\n, (float)ht-count / ht-size, LOAD_FACTOR, ht-size * 2); resize(ht); } insertNoResize(ht, key); } // 打印 void printHashTable(HashTable *ht) { printf(哈希表 (size%d, count%d, load%.2f):\n, ht-size, ht-count, (float)ht-count / ht-size); for (int i 0; i ht-size; i) { printf([%d]: , i); Node *cur ht-buckets[i]; while (cur ! NULL) { printf(%d - , cur-key); cur cur-next; } printf(NULL\n); } } // 销毁 void destroyHashTable(HashTable *ht) { for (int i 0; i ht-size; i) { Node *cur ht-buckets[i]; while (cur ! NULL) { Node *temp cur; cur cur-next; free(temp); } } free(ht-buckets); free(ht); } int main() { HashTable *ht createHashTable(); printf( 插入元素观察扩容\n); int values[] {25, 35, 45, 17, 28, 19, 33, 42, 51, 67, 73, 88}; for (int i 0; i 12; i) { insert(ht, values[i]); printf(插入 %d\n, values[i]); } printf(\n); printHashTable(ht); printf(\n 查找测试 \n); printf(查找25: %s\n, search(ht, 25) ? 找到 : 未找到); printf(查找99: %s\n, search(ht, 99) ? 找到 : 未找到); destroyHashTable(ht); return 0; }运行结果text 插入元素观察扩容 插入 25 插入 35 插入 45 插入 17 插入 28 插入 19 插入 33 负载因子 0.75 0.75扩容到 16 插入 42 插入 51 插入 67 插入 73 插入 88 哈希表 (size16, count12, load0.75): [0]: 64 - NULL [1]: 33 - 17 - 25 - NULL [2]: 42 - 18 - NULL [3]: 35 - 19 - 51 - 67 - NULL ...六、两种冲突解决方法的对比对比项链地址法开放地址法原理冲突元素用链表存储找下一个空位空间利用率需要额外指针100%利用删除操作简单复杂需标记删除聚集问题无有线性探测性能稳定性稳定可能退化实现复杂度中等较低适用场景通用数据量可预估七、复杂度分析操作平均时间复杂度最坏时间复杂度插入O(1)O(n)查找O(1)O(n)删除O(1)O(n)最坏情况所有key映射到同一个位置退化成链表。如何保证平均O(1)哈希函数分布均匀负载因子控制在0.75以下适时扩容八、小结这一篇我们学习了哈希表要点说明哈希函数除留余数法最常用size取质数链地址法数组链表实现简单性能稳定开放地址法线性探测、二次探测无额外指针负载因子扩容阈值通常0.75时间复杂度平均O(1)最坏O(n)哈希表的核心好的哈希函数让数据分布均匀合理的负载因子保证性能冲突解决策略影响实现复杂度下一篇我们讲排序算法概述与插入排序。九、思考题除留余数法中为什么模数取质数能减少冲突链地址法和开放地址法哪种删除操作更简单为什么如果负载因子超过1链地址法和开放地址法分别会发生什么如何设计一个字符串的哈希函数欢迎在评论区讨论你的答案。

相关文章:

【数据结构与算法】第30篇:哈希表(Hash Table)

一、什么是哈希表1.1 基本思想哈希表通过哈希函数将关键字映射到数组的某个位置,实现快速访问。textkey → 哈希函数 → 数组下标 → 访问/存储示例:hash(key) key % 10key25 → 25%105 → 存入下标5key37 → 37%107 → 存入下标71.2 哈希冲突不同的key…...

【数据结构与算法】第29篇:红黑树原理与C语言模拟

一、红黑树的定义1.1 五大性质红黑树是一种自平衡二叉查找树,每个节点增加一个颜色属性(红或黑),必须满足:性质说明性质1每个节点是红色或黑色性质2根节点是黑色性质3所有叶子节点(NIL)是黑色性…...

回溯算法双杀:子集 + 电话号码的字母组合 | 经典模板题解析

目录 一、LeetCode 78:子集 题目描述 核心思路(回溯法) 完整代码 关键解析 二、LeetCode 17:电话号码的字母组合 题目描述 核心思路(回溯法) 完整代码 关键解析 三、两道题核心对比 总结 一、L…...

算法双杀:Trie(前缀树)实现 + 全排列(回溯经典)| 面试必刷模板题

目录 一、Trie(前缀树):字符串查询的效率神器 什么是前缀树? 核心设计 完整实现代码 关键解析 二、全排列:回溯算法入门经典 题目描述 核心思路(回溯法) 完整实现代码 关键解析 三、…...

ROS Noetic下,用DWA和TEB调教你的机器人:move_base局部规划器参数实战避坑指南

ROS Noetic下DWA与TEB局部规划器参数调优实战指南 1. 理解局部规划器的核心作用 在ROS导航堆栈中,局部规划器扮演着机器人运动控制的"末梢神经"角色。当全局规划器生成了一条从起点到终点的理想路径后,局部规划器负责根据实时环境信息&#xf…...

医学图像分类与诊断数据集5040张VOC+YOLO

医学图像分类与诊断数据集5040张VOCYOLO数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):5040 标注数量(xml文件个数):5040 标注数…...

用STM32F103RCT6和AD9959搞定电赛C题:一个无线信号模拟系统的完整搭建与调试实录

从零构建电赛C题无线信号模拟系统:STM32F103RCT6与AD9959实战全记录 全国大学生电子设计大赛的C题向来以高难度和综合性著称,今年的无线信号模拟系统题目更是让不少参赛队伍挠头。作为一支从零开始的团队,我们在四天三夜的极限时间里&#xf…...

零信任架构下的企业数据安全防护体系设计与实践

1. 零信任架构:企业数据安全的新范式 过去十年我见过太多企业安全事件,根源往往在于传统边界防护的失效。某次给金融客户做安全评估时发现,他们花重金部署的防火墙就像个筛子——攻击者通过一个普通员工的钓鱼邮件就长驱直入,最终…...

终极魔兽争霸3性能优化指南:从卡顿到180帧的完整解决方案

终极魔兽争霸3性能优化指南:从卡顿到180帧的完整解决方案 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 魔兽争霸3作为经典RTS游戏&#…...

Agent 中的记忆系统:短期记忆、长期知识库与情境缓存最佳实践

Agent 中的记忆系统:短期记忆、长期知识库与情境缓存最佳实践 摘要/引言 开门见山:当我们说AI Agent要“有记忆”时,我们在说什么? 你有没有过这样的经历:和OpenAI的ChatGPT连续聊了20轮Python爬虫优化,…...

Virtuoso ADE L仿真结果分析实战:用Calculator快速提取带宽、相位裕度和噪声

Virtuoso ADE L仿真结果深度解析:从波形到关键指标的实战技巧 面对仿真完成后满屏的波形曲线,许多工程师常陷入"数据丰富但信息匮乏"的困境。本文将聚焦两级运放案例,演示如何用Calculator函数精准提取GBW、相位裕度、噪声谱密度等…...

lil_tea c++ 2023 style guide

调试 我觉得调试是最重要的, 所以放在最开头. 调试, 最最最重要的, sudo apt remove gdb (这只是个玩笑, 不要真的执行). 深入学习贯彻 fail fast 原则, 在出现错误时直接退出程序, 而不是使用 try throw catch. 编写程序的时候假设所有东西不会出错, 然后每当出现程序异常退…...

Debian 12 内网求生记:手把手搞定1Panel离线安装与Docker启动(附iptables补丁)

Debian 12 内网求生记:手把手搞定1Panel离线安装与Docker启动(附iptables补丁) 1. 内网环境下的技术挑战 在完全隔离的内网环境中部署现代化运维工具,就像在没有GPS的荒野中寻找方向。我们面对的不仅是网络连接的缺失,…...

中国AI Agent发展现状与生态分析

中国AI Agent发展现状与生态分析 1. 标题 (Title) [从“工具助手”到“决策伙伴”:全景拆解中国AI Agent的爆发逻辑、玩家图谱与下一个十年机遇][万字深度:202X中国AI Agent发展白皮书——技术攻坚、商业落地与生态全景解析][抢滩AGI入口之战&#xff1a…...

2026教培行业项目管理系统盘点:8款课程研发协同工具横评

本文将深入对比8款适合教育培训行业的项目管理工具:Worktile、Asana、monday.com、ClickUp、Jira、Confluence、Notion、Smartsheet。文章将围绕教研管理、课程开发协同、文档沉淀、进度追踪、安全合规与部署方式等维度展开分析,帮助教育培训机构判断不同…...

视觉化看板工具怎么选?9 款创意团队项目协作平台优势分析

本文将深入对比 9 款支持视觉化看板的项目协作工具:Worktile、Trello、Asana、monday.com、ClickUp、Wrike、Notion、Jira、Teambition,重点分析它们在创意团队中的项目管理能力、适用场景、部署方式、协作效率与安全合规差异,帮助企业选型者…...

高效智能激活解决方案:KMS_VL_ALL_AIO如何一键解决Windows与Office授权难题

高效智能激活解决方案:KMS_VL_ALL_AIO如何一键解决Windows与Office授权难题 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 你是否曾因Windows突然弹出激活提醒而中断工作&#xff1…...

NsEmuTools:如何用一款工具解决NS模拟器90%的配置难题?

NsEmuTools:如何用一款工具解决NS模拟器90%的配置难题? 【免费下载链接】ns-emu-tools 一个用于安装/更新 NS 模拟器的工具 项目地址: https://gitcode.com/gh_mirrors/ns/ns-emu-tools 当我们谈论NS模拟器时,大多数玩家首先想到的是Y…...

深度解析WaveTools:鸣潮游戏性能优化与数据分析的专业工具

深度解析WaveTools:鸣潮游戏性能优化与数据分析的专业工具 【免费下载链接】WaveTools 🧰鸣潮工具箱 项目地址: https://gitcode.com/gh_mirrors/wa/WaveTools WaveTools作为一款专为《鸣潮》游戏设计的开源工具箱,通过帧率解锁、画质…...

DeepSeek-OCR-2功能体验:双列可视化界面,左传图右看结果,操作直观

DeepSeek-OCR-2功能体验:双列可视化界面,左传图右看结果,操作直观 1. 为什么这个OCR工具值得一试 如果你经常需要处理扫描文档、PDF文件或者图片中的文字,传统OCR工具可能让你又爱又恨。它们确实能提取文字,但遇到复…...

为什么工业 AI 必须引入本体论?

如果你只用大语言模型(LLM)写周报、画插图、做视频,你只需要关心它聪不聪明。但如果你要用它去设计一座造价上亿的芯片工厂、去控制百万集群算力中心的液冷系统。你就必须回答:AI 凭什么保证绝对不出错?大模型的数学本…...

降AI后格式乱了怎么修:Word格式修复操作指南

降AI后格式乱了怎么修:Word格式修复操作指南 上周室友第一次用降AI工具,操作错了好几步,差点浪费机会。觉得有必要写一篇详细教程。 我用的是嘎嘎降AI(www.aigcleaner.com),4.8元一篇,达标率9…...

论文降AI之前要做哪些AIGC自检:完整自查流程

论文降AI之前要做哪些AIGC自检:完整自查流程 被问了太多次降AI前自检相关的问题,写一篇完整教程。 主要工具是嘎嘎降AI(www.aigcleaner.com),4.8元。第一次用的话有些细节知道和不知道差别挺大的。 操作前准备 开始…...

RetDec反编译神器:从零开始掌握二进制代码逆向分析

RetDec反编译神器:从零开始掌握二进制代码逆向分析 【免费下载链接】retdec RetDec is a retargetable machine-code decompiler based on LLVM. 项目地址: https://gitcode.com/gh_mirrors/re/retdec 你是否曾经面对一个神秘的二进制文件,想要了…...

三步掌握Alienware终极控制权:AlienFX Tools新手完全指南

三步掌握Alienware终极控制权:AlienFX Tools新手完全指南 【免费下载链接】alienfx-tools Alienware systems lights, fans, and power control tools and apps 项目地址: https://gitcode.com/gh_mirrors/al/alienfx-tools 你是否厌倦了Alienware官方软件的…...

Windows电脑安装安卓APK的终极指南:3分钟学会跨平台应用安装

Windows电脑安装安卓APK的终极指南:3分钟学会跨平台应用安装 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 还在为手机应用无法在电脑上使用而烦恼吗&…...

从输入法到天气预测:一阶与高阶马尔科夫链的建模实战

1. 马尔科夫链:从输入法到天气预测的数学魔法 第一次听说马尔科夫链这个词时,我正盯着手机输入法发呆。当时在打"奥利奥"这个词,刚输入"ao"就自动联想出"奥利奥",而前一天我还在为打不出这个词抓耳…...

自适应交易利器:KAMA指标在Python中的高效实现与实战解析

1. 认识KAMA指标:让移动平均线"活"起来 第一次接触KAMA指标是在2018年的一个量化交易项目中。当时我们团队正在寻找能够适应不同市场环境的趋势指标,传统的均线系统在震荡市中频繁发出假信号,而在趋势行情中又显得过于滞后。直到一…...

边缘检测数据集BSDS500的‘坑’与优化:多标注者标签融合与阈值选择的经验谈

边缘检测数据集BSDS500的‘坑’与优化:多标注者标签融合与阈值选择的经验谈 第一次接触BSDS500数据集时,我以为这不过又是一个标准的边缘检测基准——直到我的RCF网络在验证集上输出了支离破碎的边缘图。那个深夜调试参数的场景至今记忆犹新:…...

前端框架选择:别再被营销号忽悠了

前端框架选择:别再被营销号忽悠了 一、引言 又到了我这个毒舌工匠上线的时间了!今天咱们来聊聊前端框架选择这个话题。现在市面上的前端框架太多了,React、Vue、Angular、Svelte、Solid等等,营销号每天都在吹这个好那个好&#xf…...