快捷搜索:  as

说说Python中字典和散列表,散列冲突的解决原理

散列表

Python 用散列表来实现 dict。散列表着实是一个稀疏数组(老是有空缺元素的数组称为稀疏数组)。在一样平常书中,散列表里的单元平日叫做表元(bucket)。在 dict 的散列表傍边,每个键值对都占用一个表元,每个表元都有两个部分,一个是对键的引用,一个是对值的引用。由于每个表元的大年夜小同等,以是可以经由过程偏移量来读取某个表元。

Python 会设法包管大年夜概还有三分之一的表元是空的,当快要达到这个阀值的时刻,会进行扩容,将原散列表复制到一个更大年夜的散列表里。

假如要把一个工具放入到散列表里,就先要谋略这个元素键的散列值。这就要求键(key)必须是可散列的。

一个可散列的工具必须满意以下前提:

支持 hash() 函数,并且经由过程 hash() 措施所获得的散列值是不变的。

支持经由过程 eq() 措施来检测相等性。

若 a == b 为真,则 hash(a) == hash(b) 也为真。

散列表的算法:

为了获取键 search_key 所对应的值 search_value,Python 会首先调用 hash(search_key) 谋略 search_key 的散列值,把这个值最低的几位数字算作偏移量,在散列表里查找表元(详细取几位,得看当前散列表的大年夜小)。若找到的表元是空的,则抛出 KeyError 非常;若不为空,则表元里会有一对 found_key:found_value,查验 search_key 和 found_key 是否相等,若相等,则返回 found_value。若不相等,这种环境称为散列冲突。

为了办理散列冲突,算法会在散列值中别的再取几位,然后用特殊的措施处置惩罚一下,把获得的新数值作为偏移量在散列表中查找表元,若找到的表元是空的,则同样抛出 KeyError 非常;若非空,则对照键是否同等,同等则返回对应的值;若又发明散列冲突,则重复以上步骤。

添加新元素跟上面的历程险些一样,只不过在发明空表元的时刻会放入这个新元素,不为空则为散列冲突,继承查找。

为什么字典是无序的

当往 dict 里添加新元素并且发生了散列冲突的时刻,新元素可能会被安排寄放到另一个位置。于是就会发生下面的环境:dict([key1, value1], [key2, value2]) 和 dict([key2, value2], [key1, value1]) 两个字典,在进行对照的时刻是相等的,但假如 key1 和 key2 散列冲突,则这两个键在字典里的顺序是不一样的(由于添加的顺序不一样,先添加的先盘踞第一次散列值的位置,后添加的)。

无论何时,往 dict 里添加新的键,Python 解析器都可能做出为字典扩容的抉择。扩容导致的结果便是要新建一个更大年夜的散列表,并把字典里已有的元素添加到新的散列表里。这个历程中可能发生新的散列冲突,导致新散列表中键的序次变更。

假如在迭代一个字典的同时往里面添加新的键,会发生什么?不凑巧扩容了,不凑巧键的序次变了,然后就 orz 了。

总结

散列表是一个在光阴和空间上做出权衡的经典例子。假如没有空间(内存)的限定,那么可以直接将键作为数组的索引。那么所有的查找光阴繁杂度为 O(1);假如没有光阴的限定,那么可以直接用数组,这样只必要很少的内存。

您可能还会对下面的文章感兴趣: