为什么Python使用哈希表实现dict,而不实现Red-Black Tree?[关闭]


11

为什么Python使用哈希表实现dict,而不实现Red-Black Tree?

关键是什么?性能?


2
分享您的研究成果对所有人都有帮助。告诉我们您尝试过的内容以及为什么它不能满足您的需求。这表明您已花时间尝试自我帮助,这使我们免于重复显而易见的答案,并且最重要的是,它可以帮助您获得更具体和相关的答案。另请参阅“ 如何提问
gna 2014年

Answers:


16

这是一个通用的,非特定于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 == bid(a) > id(c),则它也必须遵循该顺序id(b) > id(c),此处不能保证。因此,我们可以使用键的哈希码作为查找键。在这里,排序工作正常,但是我们最终可能会得到带有相同哈希码的多个不同键,这些键将分配给RB树中的同一节点。为了解决这些哈希冲突,我们可以像哈希表一样使用单独的链接,但这也继承了哈希表的最坏情况行为–两全其美。

其他方面

  • 我希望散列表比树具有更好的内存位置,因为散列表本质上只是一个数组。

  • 两种数据结构中的条目都有相当高的开销:

    • 哈希表:单独链接时的键,值和下一个条目指针。同时存储哈希码可以加快大小调整。
    • RB树:键,值,颜色,左子指针,右子指针。请注意,虽然颜色只是一点点,但对齐问题可能意味着您仍然会浪费足够的空间来容纳几乎整个指针,甚至当只能分配2幂次方的内存块时甚至浪费了将近4个指针。无论如何,RB树条目比散列表条目消耗更多的内存。
  • RB树中的插入和删除涉及树的旋转。这些并不是真的很昂贵,但是确实会产生开销。在散列中,插入和删除并不比简单访问昂贵(尽管在插入时调整散列表的大小是一种O(n)努力)。

  • 哈希表本质上是可变的,而RB树也可以以不变的方式实现。但是,这很少有用。


我们可以有一个带有少量RB树的哈希表来碰撞哈希吗?
aragaer 2014年

@aragaer通常不会,但是在某些特定情况下是可能的。但是,冲突通常由链表处理-实施起来更容易,开销要少得多并且性能通常要好得多,因为我们通常只有很少的冲突。如果我们预计会发生许多冲突,则可以更改哈希函数,或使用更简单的B树。自平衡树(如RB树)很棒,但是在许多情况下,它们根本无法增加价值。
阿蒙

树需要支持“ <”的对象。哈希表需要支持哈希+“ =”的对象。因此,RB树可能无法实现。但是实际上,如果您的哈希表有大量冲突,那么您需要一个新的哈希函数,而不是碰撞键的替代算法。
gnasher729

1

还有的原因为何整个范围可能是真实的,但关键的有可能是:

  • 哈希表比树更易于实现。两者都不是完全无关紧要的,但是哈希表要容易一些,并且由于只需要一个哈希函数和一个相等函数,对合法密钥域的影响就不那么严格了。树需要一个总阶函数,这很难编写。
  • 哈希表(可能)在小尺寸下具有更好的性能。这很重要,因为很大一部分工作理论上只处理大型数据集。实际上,实际上只有几十个或几百个键才能工作,而不是数百万个。小规模性能非常重要,您不能使用渐近分析来找出最佳方法。您必须实际实施和衡量。

在典型的用例中,更容易编写/维护,并且还是性能优胜者?请注册我!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.