python字典原理
hash表原理
哈希表(Hash Table)是一种数据结构,用于实现键值对(key-value pair)的存储和检索。其原理基于哈希函数,通过将键(key)映射到数组(或者称为哈希表)的特定位置来实现快速的查找。
通过一个简单的示例来说明哈希表的工作原理。
假设我们有一个哈希表,其大小为5,使用的哈希函数是取余数法。我们将存储一些字符串作为键,并将它们哈希到哈希表中。
-
哈希函数:我们使用简单的取余数法,将字符串的ASCII值相加并对哈希表的大小取余,以确定键的位置。
-
哈希表:我们有一个大小为5的哈希表,每个位置称为桶。初始时,哈希表为空。
-
插入操作:我们要插入几个键值对到哈希表中:
- 键:“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") -
查找操作:要查找键为"cat"的值,我们首先通过哈希函数确定它的位置为 2,然后在哈希表的第 2 个桶以及其后面的链表中搜索,找到对应的值为"black"。
-
删除操作:要删除键为"apple"的键值对,我们首先找到它的位置为 2,然后在哈希表的第 2 个桶以及其后面的链表中找到并删除。
哈希表通过这种方式实现了快速的插入、查找和删除操作,尽管存在冲突,但在设计良好的情况下,冲突的影响可以最小化。
hash冲突
哈希冲突指的是当两个或多个不同的键经过哈希函数计算后,得到相同的哈希值,并尝试存储在哈希表的同一位置时发生的情况。哈希冲突是在使用哈希表时不可避免的,因为在大多数情况下,哈希函数的输出范围要远远小于键的可能取值范围,因此多个键可能被映射到同一个哈希桶中。
解决哈希冲突的常见方法有两种:
-
开放寻址法(Open Addressing)
在这种方法中,如果发生冲突,就会尝试在哈希表中的其他位置中寻找可用的空闲位置,直到找到一个空闲位置来存储冲突的键值对。这种方法需要一个良好的探测序列,以确保所有位置都能被探测到,且不会形成循环。常见的开放寻址方法包括线性探测、二次探测和双重散列。
- 线性探测:当发生哈希冲突时,线性探测会按顺序检查哈希表中的下一个位置,直到找到一个空的位置为止。
- 二次探测:二次探测会根据一个二次函数来探测下一个位置,而不是像线性探测那样简单地按顺序探测。
-
链表法(Chaining):在这种方法中,哈希表的每个桶都是一个链表的头指针,当发生冲突时,新的键值对被添加到该桶对应的链表中。这样,所有哈希到同一位置的键值对都被存储在同一个桶的链表中。链表法是最常用的解决哈希冲突的方法之一,它简单且易于实现,并且能够在大多数情况下提供良好的性能。
注:在java中,链接地址法也是HashMap解决哈希冲突的方法之一,jdk1.7完全采用单链表来存储同义词,jdk1.8则采用了一种混合模式,对于链表长度大于8的,会转换为红黑树存储。
-
再哈希法
就是同时构造多个不同的哈希函数,当第一个hash函数发生冲突时,再用第二个hash函数进行计算,直到冲突不再产生,这种方法不易产生聚集,但是增加了计算时间。 -
建立公共溢出区
将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。
在实际应用中,选择哪种解决哈希冲突的方法通常取决于哈希表的大小、负载因子以及特定问题的要求。
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]
]
-
indices 列表:
indices是一个长度为哈希表大小的列表,用于记录每个存储桶的索引信息。- 每个元素可以是一个索引值,指向
entries列表中的一个键值对,或者为None,表示该位置没有键值对。
-
entries 列表:
entries是一个用于存储键值对的列表,每个元素都是一个包含哈希值、键和值的列表或元组。- 每个元素
[hash, key, value]存储了一个键值对的哈希值、键和对应的值。
通过 indices 和 entries 两个列表的结合,可以实现一个简单的哈希表数据结构。其中,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。

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

被折叠的 条评论
为什么被折叠?



