Hello 算法图解:基于数组实现哈希表 ArrayHashMap——桶、哈希函数与增删查操作的完整实现

发布时间:2026/11/5 11:41:45

Hello 算法图解:基于数组实现哈希表 ArrayHashMap——桶、哈希函数与增删查操作的完整实现 Hello 算法图解基于数组实现哈希表 ArrayHashMap——桶、哈希函数与增删查操作的完整实现【免费下载链接】hello-algo《Hello 算法》动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語提供 Python, Java, C, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址: https://gitcode.com/GitHub_Trending/he/hello-algo本篇基于 hello-algo 仓库中codes/pythontutor/chapter_hashing/array_hash_map.md内嵌的 Python 实现系统讲解“仅用一个数组就能实现哈希表”的完整方案桶bucket与键值对Pair的组织方式、key % capacity哈希函数的工作原理以及get / put / remove和遍历操作的源码级细节。读完你既能看懂这版ArrayHashMap的每行代码也能理解它的局限——哈希冲突与扩容负载因子——并知道下一步该如何演进。一、为什么可以先用一个数组实现哈希表哈希表hash table又称散列表通过建立键key与值value的映射在 $O(1)$ 时间内完成查询。这是它相对数组、链表的决定性优势三者的效率对比如下见 docs/chapter_hashing/hash_map.md数组链表哈希表查找元素$O(n)$$O(n)$$O(1)$添加元素$O(1)$$O(1)$$O(1)$删除元素$O(n)$$O(n)$$O(1)$最简单的哈希表实现思路是只用一个数组充当存储容器把数组中的每个空位称为“桶bucket”每个桶恰好存放一个键值对。查询操作因此退化为两步通过某种哈希算法hash()计算得到哈希值将哈希值对桶数量数组长度capacity取模得到该key对应桶数组索引indexindex hash(key) % capacity随后即可用index直接访问数组取出value。整个过程没有任何搜索或遍历这就是 $O(1)$ 查询的来源。二、ArrayHashMap 完整源码解析关联文档codes/pythontutor/chapter_hashing/array_hash_map.md中内嵌的完整实现如下仓库可运行的等价版本见 codes/python/chapter_hashing/array_hash_map.pyclass Pair: 键值对 def __init__(self, key: int, val: str): self.key key self.val val class ArrayHashMap: 基于数组实现的哈希表 def __init__(self): 构造方法 # 初始化数组包含 20 个桶 self.buckets: list[Pair | None] [None] * 20 def hash_func(self, key: int) - int: 哈希函数 index key % 20 return index def get(self, key: int) - str | None: 查询操作 index: int self.hash_func(key) pair: Pair self.buckets[index] if pair is None: return None return pair.val def put(self, key: int, val: str): 添加操作 pair Pair(key, val) index: int self.hash_func(key) self.buckets[index] pair def remove(self, key: int): 删除操作 index: int self.hash_func(key) # 置为 None 代表删除 self.buckets[index] None def entry_set(self) - list[Pair]: 获取所有键值对 result: list[Pair] [] for pair in self.buckets: if pair is not None: result.append(pair) return result def key_set(self) - list[int]: 获取所有键 result [] for pair in self.buckets: if pair is not None: result.append(pair.key) return result def value_set(self) - list[str]: 获取所有值 result [] for pair in self.buckets: if pair is not None: result.append(pair.val) return result def print(self): 打印哈希表 for pair in self.buckets: if pair is not None: print(pair.key, -, pair.val)逐部分来看1. Pair键值对的封装key和value被封装成类Pair以表示一个不可拆分的键值对。之所以需要这个中间类型是因为数组的每个桶只能存“一个对象”而键和值必须同时被保留否则遍历时无法同时拿到Key - Value。2. 构造方法与桶数组self.buckets [None] * 20初始化了一个长度为 20 的数组即capacity 20每个元素要么是None空桶要么是一个Pair。注意内嵌在 pythontutor 文档中的这一版取 20 个桶而仓库中可运行的 array_hash_map.py 以及同目录下的 Java 实现、C 实现 均按文档正文的示例取capacity 100如 C 版中的#define MAX_SIZE 100。这个差异直接影响后文的索引计算分析示例时需注意。3. 哈希函数取模就是最简 hashhash_func采用hash(key) key的恒等哈希算法再对容量取模即index key % 20。这正是正文公式index hash(key) % capacity的直接落地。取模保证了输出必然落在[0, capacity)区间内与数组索引一一对应。4. get / put / remove$O(1)$ 的增删查三个核心操作的结构完全对称都是“算索引 → 直接访问桶”get(key)先算index hash_func(key)取self.buckets[index]若桶为空None返回None否则返回pair.val。没有命中任何桶时不会抛错而是返回空值由调用方判断。put(key, val)构造Pair后写入self.buckets[index]。从源码结构看这里不做“键是否已存在”的判断同一桶的新pair会直接覆盖旧值——所以put兼具“添加和更新”语义但更新的前提是两个key恰好映射到同一桶。remove(key)同样只按索引定位把该桶置为None即视为删除。它不会检查桶中存的key是否就是要删的那个这一点在冲突场景下会引出问题见第四节。5. entry_set / key_set / value_set三种遍历视图这三个方法都是线性扫描整个桶数组、跳过None后收集结果时间复杂度为 $O(n)$$n$ 为桶数entry_set()返回所有Pair对象对应内置dict的items()key_set()只收集pair.key对应keys()value_set()只收集pair.val对应values()。print()方法则是entry_set逻辑的内联版本逐桶打印key - value。三、运行驱动代码从示例键值对看哈希定位过程文档内嵌代码的驱动部分if __name__ __main__:演示了“添加 → 查询 → 删除 → 遍历”的完整流程# 初始化哈希表 hmap ArrayHashMap() # 添加操作 hmap.put(12836, 小哈) hmap.put(15937, 小啰) hmap.put(16750, 小算) hmap.put(13276, 小法) hmap.put(10583, 小鸭) # 查询操作 name hmap.get(15937) # 删除操作 hmap.remove(10583) # 遍历哈希表 print(\n遍历键值对 Key-Value) for pair in hmap.entry_set(): print(pair.key, -, pair.val)以capacity 100的仓库版本为例可以手算每个学号落到的桶键 keykey % 100落桶索引值128363636小哈159373737小啰167505050小算132767676小法105838383小鸭五个键各占一个桶因此hmap.get(15937)直接读取buckets[37]返回小啰hmap.remove(10583)将buckets[83]置为Noneentry_set()遍历后只剩 4 个Pair按桶下标顺序输出。这套示例数据也解释了为什么文档选择“学号 → 姓名”作为主题整型学号天然适合取模定位且数值间差异能直观展示哈希函数的分散效果。四、简单实现的边界哈希冲突与扩容从本质上看哈希函数是把所有key构成的输入空间映射到数组索引构成的输出空间而输入空间远大于输出空间因此一定存在“多个输入对应相同输出”的情况。以取模哈希为例当输入的key后两位相同时capacity 100时哈希函数的输出结果也相同例如12836 % 100 36 20336 % 100 36两个不同的学号指向了同一个桶这就是哈希冲突hash collision。上面的简单实现对冲突没有任何处理手段put只会让后来的键值对覆盖先前的键值对remove也可能误删同桶中的其他键值对——这是教学实现刻意保留的“裸奔”形态用于先把哈希函数本身的机制讲透。缓解冲突最直接的办法是扩容哈希表容量 $n$ 越大多个key落入同一桶的概率越低。类似于数组扩容哈希表扩容需要把所有键值对从原表迁移到新表并且由于capacity改变必须用哈希函数重新计算所有键值对的存储位置rehash开销显著。为此编程语言通常预留足够大的初始容量防止频繁扩容。衡量冲突严重程度、并常用作扩容触发条件的指标是负载因子load factor$$\text{负载因子} \frac{\text{元素数量}}{\text{桶数量}}$$例如在 Java 中当负载因子超过 0.75 时HashMap会将容量扩容至原先的 2 倍。而真正解决“同桶多值”的工程手段有两种均可在同一章节继续阅读链地址法chaining每个桶挂一条链表冲突的键值对依次入链见 codes/python/chapter_hashing/hash_map_chaining.py开放地址法open addressing冲突时按探测序列寻找下一个空桶见 codes/python/chapter_hashing/hash_map_open_addressing.py其中还需处理带删除标记DELETED的墓碑问题见 开放地址法图解 与 docs/chapter_hashing/hash_collision.md。五、多语言实现对照同一份ArrayHashMap设计在仓库中还有跨语言版本核心结构完全一致便于对照理解Python 版buckets是list[Pair | None]删除时置NoneJava 版ListPair充当桶数组空桶为null删除时buckets.set(index, null)且遍历方法命名为pairSet / keySet / valueSetC 版用Pair *buckets[MAX_SIZE]指针数组实现Pair是key(int) val(char*)结构体并提供了显式的newArrayHashMap / delArrayHashMap构造与析构函数管理内存。三种实现共同印证了本文的核心结论哈希表的最小内核就是一个桶数组 一个取模哈希函数其余语言特性引用类型、指针管理都只是表层差异。六、小结围绕codes/pythontutor/chapter_hashing/array_hash_map.md这份实现可以沉淀出以下要点结构ArrayHashMap 桶数组buckets 键值对封装Pair空桶以None表示定位index hash(key) % capacity本文恒等哈希加取模即最简哈希函数操作get / put / remove均为 $O(1)$ 的直接寻址entry_set / key_set / value_set为 $O(n)$ 的线性收集局限该实现对哈希冲突不做处理后写覆盖先写演进方向通过扩容降低冲突概率以负载因子如 Java 的 0.75 阈值触发扩容再以链地址法或开放地址法容纳同桶多值。如需继续深入哈希表的完整设计冲突解决、扩容 rehash、内置哈希表使用姿势建议直接阅读 docs/chapter_hashing/hash_map.md 与 docs/chapter_hashing/hash_collision.md 两篇文档及对应的多语言代码目录 codes/python/chapter_hashing/。【免费下载链接】hello-algo《Hello 算法》动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語提供 Python, Java, C, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址: https://gitcode.com/GitHub_Trending/he/hello-algo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/11/4 14:50:58

IOPaint 低内存模式实战:4GB 显存也能跑 Stable Diffusion

IOPaint 低内存模式实战:4GB 显存也能跑 Stable Diffusion 【免费下载链接】IOPaint Image inpainting tool powered by SOTA AI Model. Remove any unwanted object, defect, people from your pictures or erase and replace(powered by stable diffusion) any th…

2026/11/5 11:39:15

PostgreSQL数据类型选择指南与性能优化实践

1. PostgreSQL数据类型概述PostgreSQL作为一款功能强大的开源关系型数据库,其数据类型系统之丰富远超大多数同类产品。我在实际项目中最常遇到的问题是:面对十几种数字类型、多种字符串类型以及各种特殊类型时,开发团队往往凭直觉选择&#x…

2026/11/5 11:34:12

Simulink S-function执行时序与状态管理原理详解

简介:本资源是面向MATLAB/Simulink初学者与控制系统建模学习者的S-Function入门实践包,聚焦自定义系统函数开发这一核心能力,解决标准库无法覆盖的特定数学运算与动态逻辑建模需求。压缩包共4个文件(2个M文件实现核心算法逻辑&…

2026/11/5 11:34:12

低空经济三维智能感知与实时调度技术解析

1. 低空经济三维空间智能感知与管控体系概述低空经济作为新兴经济形态,正在全球范围内快速发展。根据国际航空运输协会(IATA)预测,到2030年,全球城市空中交通市场规模将达到1.5万亿美元。在这一背景下,《低…

2026/11/5 11:34:12

链表数据结构与PTA解题技巧详解

1. 链表基础与数据结构课程实践 链表作为线性表的链式存储结构,是数据结构课程中承前启后的关键内容。在山东理工大学(SDUT)的数据结构与算法课程体系中,链表章节通常安排在顺序表之后、树结构之前,这种教学顺序设计体…

2026/11/5 11:34:12

Simulink S-Function核心机制与两个经典C示例解析

简介:本资源是面向MATLAB/Simulink初学者与控制系统建模学习者的S-Function入门实践包,聚焦自定义系统函数开发这一核心能力,解决标准库无法覆盖的特定数学运算与动态逻辑建模需求。压缩包共4个文件(2个M文件实现核心算法逻辑&…

2026/11/5 11:34:12

Mantle系统架构:云存储局部性与扩展性的突破

1. 项目概述:Mantle系统架构演进的技术突围在云存储技术高度成熟的今天,百度沧海存储团队完成的Mantle系统设计堪称一次教科书级别的技术突围。这个被SOSP25收录的存储系统创新案例,完美诠释了如何在看似饱和的技术领域实现突破——不是通过堆…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑