【问题标题】:compare Hash with Binary search tree比较哈希与二叉搜索树
【发布时间】:2010-12-06 06:15:49
【问题描述】:

我们都知道,如果哈希函数选择得当,哈希表的插入和查找时间都是 O(1)。那么,我们要使用二叉搜索树的原因是什么?仅仅因为一个完美的哈希函数很难设计?

我是怎么想出这个问题的? 我注意到标准 C++ STL 有setmap,它们是用二叉搜索树实现的,但没有散列(不是在谈论非标准的hash_sethash_map)。而Ruby只有Hash。我想了解这种差异背后的原因。

【问题讨论】:

标签: hash binary-tree


【解决方案1】:

树允许按顺序遍历。

哈希表的最坏情况性能是 O(N)(通过一个桶进行线性搜索),二分搜索受 O(log N) 的约束。

注意:这需要平衡树 - 这就是典型实现使用自平衡树的原因,例如红黑树。

虽然这种降级不太可能发生,但这并非不可能,并且在很大程度上取决于能力选择适当的散列函数和实际数据的分布。

树实现也会慢慢增长到所需的大小,而哈希图在满时开始退化(对于大多数实现,据说大约 70% 的桶被填满)。您要么需要重新散列整个表(同样,对实时应用程序不好),要么逐步移动到新表,这不是一个简单的实现。

最后,STL 可能只使用一个“基本”容器模板,即树,以避免额外的实现复杂性。

【讨论】:

  • 二叉树可以 100% 不平衡,这意味着它采用链表的形式。这意味着它的最坏情况性能是O(n)
  • @BjörnLindqvist:是的——这就是为什么基于树的容器通常使用自平衡树,例如红黑树 (en.wikipedia.org/wiki/Red%E2%80%93black_tree)
【解决方案2】:

如果你能摆脱它,你应该总是更喜欢哈希而不是二叉搜索树。哈希比树具有更高的内存开销,但它们使用的所有内存都可以分配在一个大块中。对于树,添加的每个节点都需要单独分配,这会导致高碎片化并且不利于性能。类似于您更愿意从 1 个文件中读取 1000 个字节而不是从 1000 个不同文件中读取 1 个字节。

哈希不起作用的情况是排序很重要。例如,假设您正在编写一个内存分配器,并且您将空闲的内存块存储在一个数据结构中。键是块的大小,值是指向它们的指针。

内存请求需要查看此数据结构并找到满足请求的最小(意味着排序!)块。例如,如果您有键为 10、20、30 的块并且请求 20 字节的内存,则选择第二个块。哈希图可以轻松做到这一点。

但是如果请求是 22 字节呢?由于没有值为 20 的键,因此您必须迭代整个哈希图以找到正确的键 (30),这是一个 O(n) 操作。但是如果你使用了一棵树,那么“找到比给定键大的最小键”是一个 O(log n) 操作。

【讨论】:

    【解决方案3】:

    要使用树,您需要一种方法来对树中的项目进行排序。要使用哈希表,您需要一个函数来计算哈希表中项目的哈希值。

    有趣的是,.NET 框架要求每个类都实现(或继承)GetHashCode 函数,使每个对象都可以存储在哈希表中。但是,这也给需要实现语义正确的哈希函数的开发人员增加了额外的负担,即使他们不打算对类进行哈希处理。一种解决方案是从 GetHashCode 返回一个常量值,这在语义上是正确的,但如果该函数用于散列,则效率不高。

    【讨论】:

      【解决方案4】:

      在 C++ 时代,人们仍然是数据结构和算法的核心学术方法的拥护者,因此他们更喜欢内存占用较小且易于理解的最佳和最坏情况行为的结构。

      当 Ruby 出现时,出于脚本的目的,人们意识到他们更喜欢简单而不是原始性能,而且由于哈希表允许两个数组的语义(如果您使用顺序索引作为键)和字典(如果您使用自然键),它们被认为是更通用的数据结构。

      【讨论】:

        【解决方案5】:

        STL 最初并未在容器中包含哈希表,因为哈希表更复杂——您必须在开放寻址和封闭寻址之间进行选择,更不用说哈希函数等了。当时,Stepanov 和 Stroustrup 是试图加快它的进展,以便它很快被标准接受。

        另一方面,树相对简单。众所周知,由于这些是内存数据结构,我们可以只使用二叉树而不是 B 树。然后是在 AVL 和 RB 树之间进行选择。由于更好的性能特征,我倾向于选择 RB 树,但我无法评论这两种结构的 Wikipedia 文章(AVLRB)会相对详细地告诉您更多信息。

        否则,树和哈希表适用于不同的事物。如果您需要快速插入或检索,并且不关心它们的存储顺序,那么哈希表很好。如果您需要对插入和检索的排序特征和强有力的保证,那么二叉树是不错的选择。另一个好的经验法则是剖析。由于两者的大多数用途都与接口兼容,因此分析以查看哪个可以为您提供更好的性能也有帮助。

        【讨论】:

          【解决方案6】:

          补充 peterchen 的答案,虽然理论上哈希结构在插入和删除方面更快,但在很大程度上取决于实际数据、选择的哈希函数和数据量。

          • 完美的哈希函数取决于数据的数量和分布。

          在最佳情况和最差情况之间存在较大的性能差异使它们不适合通用结构。另一方面,二叉树更容易预测,与所使用的数据量/类型无关,尽管在最佳情况下效率较低。

          【讨论】:

            【解决方案7】:

            您可以按顺序访问二叉搜索树中的数据。

            【讨论】:

              【解决方案8】:

              搜索树是有序的,哈希不是。

              【讨论】:

              • 这似乎只在遍历它时才重要。
              猜你喜欢
              • 2010-10-25
              • 2020-04-20
              • 2019-05-16
              • 2017-11-23
              • 1970-01-01
              • 1970-01-01
              • 2011-11-05
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多