Python 哈希表的实现——字典
哈喽大家好,我是咸鱼
接触过 Python 的小伙伴应该对【字典】这一数据类型都了解吧
虽然 Python 没有显式名称为“哈希表”的内置数据结构,但是字典是哈希表实现的数据结构
在 Python 中,字典的键(key)被哈希,哈希值决定了键对应的值(value)在字典底层数据存储中的位置
那么今天我们就来看看哈希表的原理以及如何实现一个简易版的 Python 哈希表
ps:文中提到的 Python 指的是 CPyhton 实现
何为哈希表?
哈希表(hash table)通常是基于“键-值对”存储数据的数据结构
哈希表的键(key)通过哈希函数转换为哈希值(hash value),这个哈希值决定了数据在数组中的位置。这种设计使得数据检索变得非常快
举个例子,下面有一组键值对数据,其中歌手姓名是 key,歌名是 value
+------------------------------+
| Key | Value |
+------------------------------+
| Kanye | Come to life |
| XXXtentacion | Moonlight |
| J.cole | All My Life |
| Lil wanye | Mona Lisa |
| Juice WRLD | Come & Go |
+------------------------------+
如果我们想要将这些键值对存储在哈希表中,首先需要将键的值转换成哈希表的数组的索引,这时候就需要用到哈希函数了
哈希函数是哈希表实现的主要关键,它能够处理键然后返回存放数据的哈希表中对应的索引
一个好的哈希函数能够在数组中均匀地分布键,尽量避免哈希冲突(两个键返回了相同的索引)
哈希函数是如何处理键的,这里我们创建一个简易的哈希函数来模拟一下(实际上哈希函数要比这复杂得多)
def simple_hash(key, size):
return ord(key[0]) % size
这个简易版哈希函数将歌手名(即 key)首字母的 ASCII 值与哈希表大小取余,得出来的值就是歌名(value)在哈希表中的索引
那这个简易版哈希函数有什么问题呢?聪明的你一眼就看出来了:容易出现碰撞。因为不同的键的首字母有可能是一样的,就意味着返回的索引也是一样的
例如我们假设哈希表的大小为 10 ,我们以上面的歌手名作为键然后执行 simple_hash(key, 10) 得到索引

可以看到,由于Juice WRLD 和 J.cole 的首字母都一样,哈希函数返回了相同的索引,这里就发生了哈希碰撞
虽然几乎不可能完全避免任何大量数据的碰撞,但一个好的哈希函数加上一个适当大小的哈希表将减少碰撞的机会
当出现哈希碰撞时,可以使用不同的方法(例如开放寻址法)来解决碰撞
应该设计健壮的哈希函数来尽量避免哈希碰撞
我们再来看其他的键,Kanye 通过 simple_hash() 函数返回 index 5,这意味着我们可以在索引 5 (哈希表的第六个元素)上找到 其键 Kanye 和值Come to life

哈希表优点
在哈希表中,是根据哈希值(即索引)来寻找数据,所以可以快速定位到数据在哈希表中的位置,使得检索、插入和删除操作具有常数时间复杂度 O(1) 的性能
与其他数据结构相比,哈希表因其效率而脱颖而出
不但如此,哈希表可以存储不同类型的键值对,还可以动态调整自身大小
Python 中的哈希表实现
在 Python 中有一个内置的数据结构,它实现了哈希表的功能,称为字典
Python 字典(dictionary,dict)是一种无序的、可变的集合(collections),它的元素以 “键值对(key-value)”的形式存储
字典中的 key 是唯一且不可变的,这意味着它们一旦设置就无法更改
my_dict = {"Kanye": "Come to life", "XXXtentacion": "Moonlight", "J.cole": "All My Life"}
在底层,Python 的字典以哈希表的形式运行,当我们创建字典并添加键值对时,Python 会将哈希函数作用于键,从而生成哈希值,接着哈希值决定对应的值将存储在内存的哪个位置中
所以当你想要检索值时,Python 就会对键进行哈希,从而快速引导 Python 找到值的存储位置,而无需考虑字典的大小
my_dict = {}
my_dict["Kanye"] = "Come to life" # 哈希函数决定了 Come to life" 在内存中的位置
print(my_dict["Alice"]) # "Come to life"
可以看到,我们通过方括号[key]来访问键对应的值,如果键不存在,则会报错
print(my_dict["Kanye"]) # "Come to life"
# Raises KeyError: "Drake"
print(my_dict["Drake"])
为了避免该报错,我们可以使用字典内置的 get() 方法,如果键不存在则返回默认值
print(my_dict.get('Drake', "Unknown")) # Unknown
在 python 中实现哈希表
首先我们定义一个 HashTable 类,表示一个哈希表数据结构
class HashTable:
def __init__(self, size):
self.size = size
self.table = [None]*size
def _hash(self, key):
return ord(key[0]) % self.size
在构造函数 __init__() 中:
size表示哈希表的大小table是一个长度为size的数组,被用作哈希表的存储结构。初始化时,数组的所有元素都被设为None,表示哈希表初始时不含任何数据
在内部函数 _hash() 中,用于计算给定 key 的哈希值。它采用给定键 key 的第一个字符的 ASCII 值,并使用取余运算 % 将其映射到哈希表的索引范围内,以便确定键在哈希表中的存储位置。
然后我们接着在 HashTable 类中添加对键值对的增删查方法
class HashTable:
def __init__(self, size):
self.size = size
self.table = [None]*size
def _hash(self, key):
return ord(key[0]) % self.size
def set(self, key, value):
hash_index = self._hash(key)
self.table[hash_index] = (key, value)
def get(self, key):
hash_index = self._hash(key)
if self.table[hash_index] is not None:
return self.table[hash_index][1]
raise KeyError(f'Key {key} not found')
def remove(self, key):
hash_index = self._hash(key)
if self.table[hash_index] is not None:
self.table[hash_index] = None
else:
raise KeyError(f'Key {key} not found')
其中,set() 方法将键值对添加到表中,而 get() 该方法则通过其键检索值。该 remove() 方法从哈希表中删除键值对
现在,我们可以创建一个哈希表并使用它来存储和检索数据:
# 创建哈希表
hash_table = HashTable(10)
# 添加键值对
hash_table.set('Kanye', 'Come to life')
hash_table.set('XXXtentacion', 'Moonlight')
# 获取值
print(hash_table.get('XXXtentacion')) # Outputs: 'Moonlight'
# 删除键值对
hash_table.remove('XXXtentacion')
# 报错: KeyError: 'Key XXXtentacion not found'
print(hash_table.get('XXXtentacion'))
前面我们提到过,哈希碰撞是使用哈希表时不可避免的一部分,既然 Python 字典是哈希表的实现,所以也需要相应的方法来处理哈希碰撞
在 Python 的哈希表实现中,为了避免哈希冲突,通常会使用开放寻址法的变体之一,称为“线性探测”(Linear Probing)
当在字典中发生哈希冲突时,Python 会使用线性探测,即从哈希冲突的位置开始,依次往后查找下一个可用的插槽(空槽),直到找到一个空的插槽来存储要插入的键值对。
这种方法简单直接,可以减少哈希冲突的次数。但是,它可能会导致“聚集”(Clustering)问题,即一旦哈希表中形成了一片连续的已被占用的位置,新元素可能会被迫放入这片区域,导致哈希表性能下降
为了缓解聚集问题,假若当哈希表中存放的键值对超过哈希表长度的三分之二时(即装载率超过66%时),哈希表会自动扩容
最后总结一下:
- 在哈希表中,是根据哈希值(即索引)来寻找数据,所以可以快速定位到数据在哈希表中的位置
- Python 的字典以哈希表的形式运行,当我们创建字典并添加键值对时,Python 会将哈希函数作用于键,从而生成哈希值,接着哈希值决定对应的值将存储在内存的哪个位置中
- Python 通常会使用线性探测法来解决哈希冲突问题
Python 哈希表的实现——字典的更多相关文章
- 04.python哈希表
python哈希表 集合Set 集合,简称集.由任意个元素构成的集体.高级语言都实现了这个非常重要的数据结构类型. Python中,它是可变的.无序的.不重复的元素的集合. 初始化 set() -&g ...
- 索引器、哈希表Hashtabl、字典Dictionary(转)
一.索引器 索引器类似于属性,不同之处在于它们的get访问器采用参数.要声明类或结构上的索引器,使用this关键字. 示例: 索引器示例代码 /// <summary> /// 存储星 ...
- Python哈希表的例子:dict、set
dict(字典) Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度. 和list比较,dic ...
- Python哈希表和解析式
目录 1. 封装和解构 1.1 封装 1.2 解构 2. 集合Set 2.1 初始化 2.2 增加 2.3 删除 2.4 遍历 2.5 并集&交集&差集&对称差集 3.字典 3 ...
- Python 中的哈希表
Python 中的哈希表:对字典的理解 有没有想过,Python中的字典为什么这么高效稳定.原因是他是建立在hash表上.了解Python中的hash表有助于更好的理解Python,因为Pytho ...
- 数据结构图解(递归,二分,AVL,红黑树,伸展树,哈希表,字典树,B树,B+树)
递归反转 二分查找 AVL树 AVL简单的理解,如图所示,底部节点为1,不断往上到根节点,数字不断累加. 观察每个节点数字,随意选个节点A,会发现A节点的左子树节点或右子树节点末尾,数到A节点距离之差 ...
- [PHP内核探索]PHP中的哈希表
在PHP内核中,其中一个很重要的数据结构就是HashTable.我们常用的数组,在内核中就是用HashTable来实现.那么,PHP的HashTable是怎么实现的呢?最近在看HashTable的数据 ...
- 使用python实现哈希表、字典、集合
哈希表 哈希表(Hash Table, 又称为散列表),是一种线性表的存储结构.哈希表由一个直接寻址表和一个哈希函数组成.哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标. 简单哈希函数: ...
- 自己动手实现 HashMap(Python字典),彻底系统的学习哈希表(上篇)——不看血亏!!!
HashMap(Python字典)设计原理与实现(上篇)--哈希表的原理 在此前的四篇长文当中我们已经实现了我们自己的ArrayList和LinkedList,并且分析了ArrayList和Linke ...
- Python 字典和集合基于哈希表实现
哈希表作为基础数据结构我不多说,有兴趣的可以百度,或者等我出一篇博客来细谈哈希表.我这里就简单讲讲:哈希表不过就是一个定长数组,元素找位置,遇到哈希冲突则利用 hash 算法解决找另一个位置,如果数组 ...
随机推荐
- 基于AIidlux平台的自动驾驶环境感知与智能预警
自动驾驶汽车又称为无人驾驶车,是一种需要驾驶员辅助或者完全不需操控的车辆. 自动驾驶分级: 自动驾驶系统的组成部分: 环境感知系统: 自动驾驶系统架构: 自动驾驶数据集: Aidlux的作用: YOL ...
- 操作过滤器—MVC中使用操作过滤器实现JWT权限认证
前言 上一篇文章分享了授权过滤器实现JWT进行鉴权,文章链接:授权过滤器-MVC中使用授权过滤器实现JWT权限认证,接下来将用操作过滤器实现昨天的JWT鉴权. 一.什么是操作过滤器? 与授权过滤器 ...
- Redis专题-队列
Redis专题-队列 首先,想一想 Redis 适合做消息队列吗? 1.消息队列的消息存取需求是什么?redis中的解决方案是什么? 无非就是下面这几点: 0.数据可以顺序读取 1.支持阻塞等待拉取消 ...
- papricice
2023-07-14 题目 题目传送门 题目大意 给定一个 \(n\) 个点的树,这 \(n\) 个点编号为 \(1\) 到 \(n\). 现在要选择断掉两条边,会形成三个连通块,假设这三个连通块内的 ...
- 【实践篇】DDD脚手架及编码规范
一.背景介绍 我们团队一直在持续推进业务系统的体系化治理工作,在这个过程中我们沉淀了自己的DDD脚手架项目.脚手架项目是体系化治理过程中比较重要的一环,它的作用有两点: (1)可以对新建的项目进行统一 ...
- 重复的dna序列
DNA序列 由一系列核苷酸组成,缩写为 'A', 'C', 'G' 和 'T'.. 例如,"ACGAATTCCG" 是一个 DNA序列 . 在研究 DNA 时,识别 DNA 中的重 ...
- Vue路由新开页面跳转和传参传递
需求:在后台管理系统首页列表项中,点击详情跳转到系统中指定菜单的路由要求新开窗口并需要带上参数查询. 第一种方法: 1 const { id } = item; 2 let routeUrl = th ...
- SonarQube系列-认证&授权的配置
参考文档:https://docs.sonarqube.org/latest/instance-administration/security/ 概述 SonarQube具有许多全局安全功能: 认证和 ...
- 如何配置.h头文件include“”相对路径
编译工程时,找的是当前main.c文件下的.h文件,如果当前路径下没有就会报错,当前路径用.\表示,上一级目录用..\表示. 如果你的main.h文件在main.c的上一级目录中Include 文件夹 ...
- DevOps平台建设的关键点是什么?
关键还是在人 找到一个「吃过猪肉,见过猪跑的」,你问他什么是猪,他自然比「没吃过猪肉,没见过猪跑的人」更了解猪.海豚海豚,你知道猪是什么样么?它都没上过陆地,这辈子都没见过猪,它哪知道猪是什么样. 有 ...
