Python 的字典 dict 和集合 set 以 hashable 作为 key,存储数据于其中,理想情况下,查询操作具有 $O(1)$ 的时间复杂度。但如果你想当然地认为,现实总是和理想差不太多,那么就可能会掉入一个陷阱之中。
操作 | 数组 (Array) | 链表 (Linked List) | 散列表 (Hash Table) |
---|---|---|---|
增(插入) | $O(n)$ (中间) / $O(1)$ (末尾) | $O(n)$ (需要遍历) | 平均 $O(1)$ |
删(删除) | $O(n)$ (中间) / $O(1)$ (末尾) | $O(n)$ (需要遍历) | 平均 $O(1)$ |
查(查找) | $O(1)$ (按索引) / $O(n)$ (按值) | $O(n)$ | 平均 $O(1)$ |
改(修改) | $O(1)$ (按索引) | $O(n)$ | 平均 $O(1)$ |
Python 实现字典和集合,利用了 hash 表。存储元素到 hash 表时,首先会用散列函数对元素计算 hash 值,然后相同 hash 值的元素,会存储在同一个链表之中。这个 hash 函数,就是元素的类上所定义的 __hash__
特殊方法,会以元素进行计算然后得到一个整数值。Python 判断一个元素是否在字典和集合中(__contains__
),还要判断元素是否相等 (__eq__
,默认比较元素的内存地址,即 id)。首先是找到元素的 hash 值所在的链表,然后对链表元素进行逐个比对,仅当相等时才认为元素在其中,在链表中查找的时间复杂度是 $O(n)$。也就是说,字典和集合在最坏情况下,查询的时间复杂度就是 $O(1 * n)$,即 $O(n)$。
这种情况就是 hash 冲突,当严重的冲突发生时,hash 表就退化为链表了。
所以,如果一大堆元素有相同的 hash 值,则存储它们的字典或集合会发生退化。