为什么Python使用哈希表实现dict,而不实现Red-Black Tree?
关键是什么?性能?
为什么Python使用哈希表实现dict,而不实现Red-Black Tree?
关键是什么?性能?
Answers:
这是一个通用的,非特定于Python的答案。
| Hash Table | Red-Black Tree |
-------+-------------+---------------------+
Space | O(n) : O(n) | O(n) : O(n) |
Insert | O(1) : O(n) | O(log n) : O(log n) |
Fetch | O(1) : O(n) | O(log n) : O(log n) |
Delete | O(1) : O(n) | O(log n) : O(log n) |
| avg :worst | average : worst |
哈希表的问题在于哈希可能会冲突。有多种解决冲突的机制,例如开放式寻址或单独的链接。绝对最坏的情况是所有键都具有相同的哈希码,在这种情况下,哈希表将降级为链接列表。
在所有其他情况下,哈希表都是很好的数据结构,易于实现并提供良好的性能。不利的一面是,可以快速增长表并重新分配其条目的实现可能会浪费几乎与实际使用的内存一样多的内存。
RB树是自平衡的,在最坏的情况下不会改变其算法复杂性。但是,它们更难以实现。它们的平均复杂度也比哈希表差。
哈希表中的所有键都必须是可哈希化的,并且彼此之间具有相等性。对于字符串或整数,这特别容易,但是扩展到用户定义的类型也相当简单。在某些语言(如Java)中,这些属性通过定义得到保证。
RB树中的密钥必须具有总顺序:每个密钥必须与任何其他密钥具有可比性,并且两个密钥必须比较较小,较大或相等。这种排序相等性必须等于语义相等性。对于整数和其他数字而言,这是简单明了的,对于字符串来说,这也相当容易(该顺序仅需保持一致,并且在外部不可观察,因此该顺序无需考虑语言环境[1]),但对于其他没有固有顺序的类型则很困难。除非有可能进行比较,否则绝对不可能拥有不同类型的键。
[1]:实际上,我在这里错了。根据某些语言的规则,两个字符串可能不等于字节,但仍等效。参见例如Unicode归一化示例,其中两个相等的字符串被不同地编码。哈希表实现无法知道Unicode字符组成对您的哈希键是否重要。
可能有人认为,RB-Tree密钥的一种廉价解决方案是先测试是否相等,然后比较身份(即比较指针)。但是,此顺序不是可传递的:如果a == b
和id(a) > id(c)
,则它也必须遵循该顺序id(b) > id(c)
,此处不能保证。因此,我们可以使用键的哈希码作为查找键。在这里,排序工作正常,但是我们最终可能会得到带有相同哈希码的多个不同键,这些键将分配给RB树中的同一节点。为了解决这些哈希冲突,我们可以像哈希表一样使用单独的链接,但这也继承了哈希表的最坏情况行为–两全其美。
我希望散列表比树具有更好的内存位置,因为散列表本质上只是一个数组。
两种数据结构中的条目都有相当高的开销:
RB树中的插入和删除涉及树的旋转。这些并不是真的很昂贵,但是确实会产生开销。在散列中,插入和删除并不比简单访问昂贵(尽管在插入时调整散列表的大小是一种O(n)
努力)。
哈希表本质上是可变的,而RB树也可以以不变的方式实现。但是,这很少有用。