根据我对Haskell的有限知识,似乎Maps(来自Data.Map)应该像其他语言中的字典或散列表一样使用,但它们被实现为自平衡二进制搜索树。
这是为什么?使用二叉树将查找时间减少到O(log(n))而不是O(1),并且要求元素在Ord中。当然有一个很好的理由,那么使用二叉树有什么好处呢?
此外:
在什么应用程序中,二叉树比哈希表更差?反过来呢?在许多情况下,一个人会比另一个人更受欢迎吗? Haskell中是否有传统的哈希表?
答案 0 :(得分:28)
如果没有可变状态,则无法有效地实现哈希表,因为它们基于数组查找。密钥是哈希的,哈希将索引确定为桶数组。在没有可变状态的情况下,将元素插入哈希表变为O(n),因为必须复制整个数组(替代的非复制实现,如DiffArray,introduce a significant performance penalty)。二叉树实现可以共享其大部分结构,因此只需要在插入上复制几个指针。
Haskell肯定可以支持传统的哈希表,只要更新是在合适的monad中。 hashtables package可能是使用最广泛的实现。
二叉树和其他非变异结构的一个优点是它们是持久的:可以保留较旧的数据副本而不需要额外的簿记。例如,这在某种事务算法中可能很有用。它们也是自动线程安全的(尽管更新在其他线程中不可见)。
答案 1 :(得分:11)
传统哈希表在其实现中依赖于内存变异。可变内存和引用透明性结束,因此将哈希表实现降级为IO
或ST
monads。通过将旧叶留在内存中并返回指向更新树的新根节点,可以持久且高效地实现树。这让我们拥有纯粹的Map
。
典型的参考是Chris Okasaki的Purely Functional Data Structures。
答案 2 :(得分:7)
这是为什么?使用二叉树将查找时间减少到O(log(n))而不是O(1)
查找只是其中一项操作;在许多情况下,插入/修改可能更重要;还有内存考虑因素。选择树表示的主要原因可能是它更适合纯函数语言。作为“真实世界哈斯克尔”puts it:
Maps为我们提供了与其他语言中哈希表相同的功能。在内部,地图实现为平衡二叉树。与哈希表相比,这是具有不可变数据的语言中更有效的表示。这是纯粹的函数式编程如何影响我们编写代码的最明显的例子:我们选择能够干净利落地表达并有效执行的数据结构和算法,但我们对特定任务的选择往往与命令式语言中的对应物不同。 / p>
此:
并要求元素位于Ord。
似乎不是一个很大的劣势。毕竟,使用哈希映射,您需要键Hashable
,这似乎更具限制性。
在什么应用程序中,二叉树比哈希表更差?反过来呢?在许多情况下,一个人会比另一个人更受欢迎吗? Haskell中是否有传统的哈希表?
不幸的是,我无法提供广泛的比较分析,但有一个hash map package,您可以在this blog post查看其实施细节和效果数据并自行决定。
答案 3 :(得分:0)
我对使用二叉树的优势的答案是:范围查询。从语义上讲,它们需要一个完整的预订单,并在算法上从平衡的搜索树组织中获利。对于简单的查找,我担心可能只有很好的Haskell特定的答案,但本身并不是很好的答案:查找(实际上是哈希)只需要一个setoid(其键类型的相等/等价),它支持有效的哈希指针(出于好的理由,在Haskell中没有排序)。像各种形式的尝试(例如,元素更新的三元尝试,批量更新的其他尝试)散列到数组(打开或关闭)通常比空间和时间上的二叉树中的元素搜索更有效。 Hashing和Tries可以一般地定义,虽然必须手工完成 - GHC不会派生它(但是?)。像Data.Map这样的数据结构往往适用于原型设计和热点之外的代码,但是在它们很热的地方,它们很容易成为性能瓶颈。幸运的是,Haskell程序员不必关心性能,只关心他们的经理。 (出于某种原因,我目前无法找到一种方法来访问80多个Data.Map函数中搜索树的密钥兑换功能:范围查询界面。我看错了地方吗?)