Python基本数据类型:python字典原理

本文详细解释了哈希表和Python字典的工作原理,包括哈希函数、冲突的处理方法(如线性探测和链表法)、Python3.6前后字典的有序性变化,以及动态扩容策略。

python字典原理

hash表原理

哈希表(Hash Table)是一种数据结构,用于实现键值对(key-value pair)的存储和检索。其原理基于哈希函数,通过将键(key)映射到数组(或者称为哈希表)的特定位置来实现快速的查找。

通过一个简单的示例来说明哈希表的工作原理。

假设我们有一个哈希表,其大小为5,使用的哈希函数是取余数法。我们将存储一些字符串作为键,并将它们哈希到哈希表中。

  1. 哈希函数:我们使用简单的取余数法,将字符串的ASCII值相加并对哈希表的大小取余,以确定键的位置。

  2. 哈希表:我们有一个大小为5的哈希表,每个位置称为桶。初始时,哈希表为空。

  3. 插入操作:我们要插入几个键值对到哈希表中:

    • 键:“apple”,值:“red”,经过哈希函数得到的位置为 2,将键值对存储在哈希表的第 2 个桶中。
    • 键:“banana”,值:“yellow”,经过哈希函数得到的位置为 0,将键值对存储在哈希表的第 0 个桶中。
    • 键:“cat”,值:“black”,经过哈希函数得到的位置为 2(与"apple"冲突),我们使用链表法,将其存储在哈希表的第 2 个桶后面的链表中。
    • 键:“dog”,值:“brown”,经过哈希函数得到的位置为 4,将键值对存储在哈希表的第 4 个桶中。

    现在,哈希表看起来可能像这样:

    Index | Bucket
    -------------
    0     | ("banana", "yellow")
    1     |
    2     | ("apple", "red") -> ("cat", "black")
    3     |
    4     | ("dog", "brown")
    
  4. 查找操作:要查找键为"cat"的值,我们首先通过哈希函数确定它的位置为 2,然后在哈希表的第 2 个桶以及其后面的链表中搜索,找到对应的值为"black"。

  5. 删除操作:要删除键为"apple"的键值对,我们首先找到它的位置为 2,然后在哈希表的第 2 个桶以及其后面的链表中找到并删除。

哈希表通过这种方式实现了快速的插入、查找和删除操作,尽管存在冲突,但在设计良好的情况下,冲突的影响可以最小化。

hash冲突

哈希冲突指的是当两个或多个不同的键经过哈希函数计算后,得到相同的哈希值,并尝试存储在哈希表的同一位置时发生的情况。哈希冲突是在使用哈希表时不可避免的,因为在大多数情况下,哈希函数的输出范围要远远小于键的可能取值范围,因此多个键可能被映射到同一个哈希桶中。

解决哈希冲突的常见方法有两种:

  1. 开放寻址法(Open Addressing)

    在这种方法中,如果发生冲突,就会尝试在哈希表中的其他位置中寻找可用的空闲位置,直到找到一个空闲位置来存储冲突的键值对。这种方法需要一个良好的探测序列,以确保所有位置都能被探测到,且不会形成循环。常见的开放寻址方法包括线性探测、二次探测和双重散列。

  • 线性探测:当发生哈希冲突时,线性探测会按顺序检查哈希表中的下一个位置,直到找到一个空的位置为止。
  • 二次探测:二次探测会根据一个二次函数来探测下一个位置,而不是像线性探测那样简单地按顺序探测。
  1. 链表法(Chaining):在这种方法中,哈希表的每个桶都是一个链表的头指针,当发生冲突时,新的键值对被添加到该桶对应的链表中。这样,所有哈希到同一位置的键值对都被存储在同一个桶的链表中。链表法是最常用的解决哈希冲突的方法之一,它简单且易于实现,并且能够在大多数情况下提供良好的性能。

    注:在java中,链接地址法也是HashMap解决哈希冲突的方法之一,jdk1.7完全采用单链表来存储同义词,jdk1.8则采用了一种混合模式,对于链表长度大于8的,会转换为红黑树存储。

  2. 再哈希法
    就是同时构造多个不同的哈希函数,当第一个hash函数发生冲突时,再用第二个hash函数进行计算,直到冲突不再产生,这种方法不易产生聚集,但是增加了计算时间。

  3. 建立公共溢出区
    将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。

在实际应用中,选择哪种解决哈希冲突的方法通常取决于哈希表的大小、负载因子以及特定问题的要求。

python字典的实现

在 Python 中,字典(Dictionary)是一种可变的数据类型,用于存储键值对(key-value pairs)。Python 字典的实现原理主要基于哈希表(Hash Table)。下面是 Python 字典的一些关键特性和原理:

Python3.6之前的无序字典

当创建一个字典时,Python 会对每个键进行哈希计算,并将其映射到相应的存储桶中。
当哈希冲突时使用开放寻址法,Python 会尝试将键插入到相邻的存储桶中,直到找到一个空的位置为止。
当字典中的元素数量达到一定阈值时,Python 会自动对字典进行扩容,以保持其性能。扩容操作会重新计算每个键的哈希值,并重新分配存储桶,以适应新的大小。

enteies = [
    ['--', '--', '--'],
    [hash, key, value],
    ['--', '--', '--'],
    ['--', '--', '--'],
    [hash, key, value],
]

Python3.6之前的有序字典

而新字典还使用了一张Indices表来辅助。下来列出新的结构:

indices = [None, None, index, None, index, None, index]
enteies = [
    [hash0, key0, value0],
    [hash1, key1, value1],
    [hash2, key2, value2]
]
  1. indices 列表

    • indices 是一个长度为哈希表大小的列表,用于记录每个存储桶的索引信息。
    • 每个元素可以是一个索引值,指向 entries 列表中的一个键值对,或者为 None,表示该位置没有键值对。
  2. entries 列表

    • entries 是一个用于存储键值对的列表,每个元素都是一个包含哈希值、键和值的列表或元组。
    • 每个元素 [hash, key, value] 存储了一个键值对的哈希值、键和对应的值。

通过 indicesentries 两个列表的结合,可以实现一个简单的哈希表数据结构。其中,indices 列表用于快速查找存储桶,而 entries 列表则存储了实际的键值对信息。
由上可以看出,新字典存储数据本身的enteies并不会稀疏,由indices来维护具体存储的位置,enteies中的数据是和插入的数据是一样的,所以新的字典是有序的。

hash冲突

当哈希冲突时Python字典采用开放寻址法(Open Addressing)中的线性探测(Linear Probing)来处理哈希冲突。当发生冲突时,Python会检查哈希表中的下一个位置,直到找到一个空槽或者遍历完整个哈希表。如果遍历完整个哈希表都没有找到空槽,那么Python会重新调整哈希表的大小(即扩容),并重新计算所有键的哈希值。

动态扩容

随着字典中元素的增加,哈希表的负载因子(即元素数量与哈希表大小的比值)会逐渐增大。当负载因子超过某个阈值时,Python会触发动态扩容操作,创建一个新的、更大的哈希表,并将原有的键值对重新哈希后插入到新的哈希表中。这个过程虽然耗时,但可以有效地减少哈希冲突,提高字典的性能。

顺序

  • Python3.6之前无序,这是因为哈希表是根据哈希值来存储键值对的,而哈希值本身并不保证顺序性。
  • Python3.6之后有序,通过增加一张数据表来单独保存数据,保证了插入顺序和遍历顺序一致

其它

字典的key能使用什么值?

Python字典的key可以使用字符串(str),整型(int),元祖(tuple)等。我们已经知道,字典是通过哈希算法来计算key的值,所以key必须为可哈希的,list不能作为字典的key,因为list是可变的及不可哈希的对象,所以不能作为字典的key。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值