Python 字典和集合的的实现:散列表

本文探讨Python字典和集合背后的散列表原理。散列表是一种稀疏数组,利用散列值进行查找。当散列冲突发生时,算法会通过额外取位并重新搜索解决。字典的键必须是可散列的,内存开销大但查询速度快。集合(set)的实现同样基于散列表,存储元素引用。添加新键可能导致字典顺序变化,而集合无序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字典和集合的效率高,和他背后的散列表是绕不开的。

散列表其实是一个稀疏数组(总是有空白元素的数组称为稀疏数组)。散列表的单元叫做表元bucket。在dict的散列表中,每个键值对都占用一个表元,每个表元都有两个部分,一个是对键的引用,一个是对于值的引用。因为表元的大小一致,所以可以通过偏移量来读取表元。python会保证大概有三分之一的表元是空的,快要达到这个阈值的时候,原有的散列表会被复制到一个更大的空间里面。

散列值的相等:

内置的hash()方法是调用对象的__hash__。如果两个对象比较的时候是相等的,那么他的散列值必须相等吗,也就是他们的__hash__相等。

比如如果1 == 1.0 为真,那么hash(1) == hash(1.0) 也要为真。

散列表的算法:

为了获取一个字典的值dict[key],首先python会调用hash(key)来计算key的散列值,把这个值的最后几位数字(具体多少位,要看散列表的大小)当做偏移量,在散列表中查找表元。若插到的表元是空的,则会抛出KeyError异常。若不为空,则表元里面会有一对found_key:found_value。这时候python校验key==found_key是否为真,如果相等的话,就会返回found_value。 字典的查找就结束了。

以上有一种情况是key和found_key不匹配的时候,这种叫做【散列冲突】,发生这种情况的原因是散列表把随机的元素映射到了几位的数字上,而散列表本身的索引有只依赖于这个数字的一部分。为了解决散列冲突,算法会在散列值中另外再多取几位,然后处理一下,用新得到的数字当做索引再次寻找表元,重复以上步骤:如果得到是空的抛出KeyError异常,如果匹配则返回值,吐过又发生了散列冲突,重复本步骤。。

以上是字典取值的算法,新增值,修改值,也是几乎一样的操作。

dict的实现和结果

  1. 键必须是可散列的

一个可散列对象必须满足:支持hash()函数,通过__hash__()得到的散列值是不变的,支持__eq__()方法来测试相等性,若a==b为真,则hash(a)==hash(b)也为真

所有用户自定义的对象默认都是可散列的,因为他们的散列值是由id()获取的,而且他们都是不相等的。

  1. 字典的内存开销巨大

由于字典是使用了散列表,散列表又必须是稀疏数组,这就是导致了在空间的效率低下,dict是典型的空间换时间。如果想存储大量的数据,元祖是更好的选择。

  1. 键查询很快

字典类型有着巨大的内存开销,也就无视数据量的快速访问。数据量大对字典的查询速度影响很小。

  1. 键的次序取决于添加的顺序

当往dict添加新键又发生了散列冲突的时候,新建可能会被安排到另一个位置。这就产生了不同的添加次序,产生的字典顺序很可能不一样。

  1. 新增键可能改变已有的顺序

无论何时在字典添加新建,python都可能做出为字典扩容的决定。扩容就会新增一个更大的散列表,把已有的元素放到新的散列表中,这个过程就可能产生新的散列冲突,导致新的字典顺序变化。

set的实现和结果

set和frozenset的实现也一样依赖散列表,但是散列表中指存放了元素的引用(就像字典中一样只放了键,没有放值)

set的特点和dict一样,也是有上面的1到5点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值