【问题标题】:How Do I Choose Between a Hash Table and a Trie (Prefix Tree)?如何在哈希表和 Trie(前缀树)之间进行选择?
【发布时间】:2010-09-19 17:46:26
【问题描述】:

因此,如果我必须在哈希表或前缀树之间进行选择,那么导致我选择其中一个的区别因素是什么。从我自己幼稚的角度来看,似乎使用 trie 有一些额外的开销,因为它没有存储为数组,但就运行时间而言(假设最长的键是最长的英文单词)它本质上可以是 O (1) (关于上限)。也许最长的英文单词是50个字符?

哈希表是即时查找一旦你得到索引。然而,散列密钥以获取索引似乎很容易花费近 50 步。

有人可以为我提供一个更有经验的观点吗?谢谢!

【问题讨论】:

  • 值得注意的是,redix 树比普通 trie 更有效,因为您不需要为每个字符串字节创建一个新分支。此外,redix 树比哈希表更好地支持“模糊”搜索,因为您在沿着路径工作时正在查看各个位。例如,00110010 可能是输入字节,但您希望包含匹配的 00111010,它只删除了一位。

标签: algorithm data-structures hashtable trie


【解决方案1】:

尝试的优点:

基础知识:

  • 可预测的 O(k) 查找时间,其中 k 是键的大小
  • 如果不存在,查找可能需要不到 k 时间
  • 支持有序遍历
  • 不需要散列函数
  • 删除很简单

新操作:

  • 您可以快速查找键的前缀、枚举具有给定前缀的所有条目等。

链接结构的优点:

  • 如果有很多公共前缀,它们需要的空间是共享的。
  • 不可变尝试可以共享结构。您可以构建一个仅沿一个分支不同的新 trie,而不是在适当的位置更新 trie,其他地方指向旧 trie。这对于并发、表的多个同时版本等很有用。
  • 不可变的 trie 是可压缩的。也就是说,它也可以通过 hash-consing 共享 suffixes 上的结构。

哈希表的优点:

  • 每个人都知道哈希表,对吧?您的系统已经有了一个很好的优化实现,比大多数用途的尝试都要快。
  • 您的密钥不需要有任何特殊结构。
  • 比明显的链接 trie 结构更节省空间(参见下面的 cmets

【讨论】:

  • 不能完全同意“比明显的链接trie结构更节省空间” - 在一般的哈希表实现中,它占用了更大的空间来包含键,而在尝试中,每个节点代表一个词。从这个意义上说,尝试更节省空间。
  • 从一个结构访问数据与从另一个结构访问数据怎么样?我在考虑缓存和位置
  • @galactica,这与我的经验相冲突:例如,在我为空间测量的所有结构中,this answer 中,trie 表现最差。这是有道理的,因为指针比字节大得多。是的,前缀共享有帮助,但它必须克服大量开销才能达到奇偶校验。更节省空间的表示可以提供很大帮助,但是我们不再讨论明显的链接结构。
  • @DariusBacon 处理电话号码计划似乎是一个合理的尝试场景。示例场景:电话号码与运营商匹配,包括。号码从一个运营商转移到另一个运营商。对于通常的字典,它可能取决于语言(普通话与英语),您需要 n-gram 和/或其他统计数据。对于押韵书,后缀树似乎也是一个不错的选择。
  • 要查找的数据的多样性非常重要。如果大部分数据值是唯一的,那么由于使用了额外的空指针,您的空间复杂度将超过哈希值。
【解决方案2】:

这完全取决于您要解决的问题。如果您需要做的只是插入和查找,请使用哈希表。如果您需要解决更复杂的问题,例如前缀相关的查询,那么 trie 可能是更好的解决方案。

【讨论】:

  • 如果哈希表和trie在查询上具有相同的复杂性,对于k长度的字符串,我们为什么要使用哈希?你能解释一下吗?
  • 在我看来,哈希表对字符串输入进行计算,而特里树对字符串输入进行地址查找。地址查找可能会错过缓存,而我认为计算完成得更快,因为它们不会命中缓存。这就是我的合理化哈哈。
【解决方案3】:

每个人都知道哈希表及其用途,但它并不完全是恒定的查找时间,它取决于哈希表有多大,哈希函数的计算复杂度。

在大多数工业场景中,即使是很小的延迟/可扩展性也很重要(例如:高频交易),为高效查找创建巨大的哈希表并不是一个优雅的解决方案。您还必须关心要针对其在内存中占用的空间进行优化的数据结构,以减少缓存未命中。

trie 更适合需求的一个很好的例子是消息中间件。您有一百万个不同类别的消息订阅者和发布者(在 JMS 术语中 - 主题或交换),在这种情况下,如果您想根据主题(实际上是字符串)过滤掉消息,您肯定不想创建哈希表拥有百万主题的百万订阅。更好的方法是将主题存储在 trie 中,因此当基于主题匹配进行过滤时,其复杂性与主题/订阅/发布者的数量无关(仅取决于字符串的长度)。我喜欢它,因为您可以创造性地使用这种数据结构来优化空间需求,从而降低缓存未命中率。

【讨论】:

    【解决方案4】:

    使用一棵树:

    1. 如果您需要自动完成功能
    2. 查找所有以“a”或“axe”开头的单词。
    3. 后缀树是一种特殊形式的树。后缀树具有哈希无法涵盖的所有优点。

    【讨论】:

      【解决方案5】:

      我没有看到任何人明确提及我认为需要牢记的重要内容。哈希表和各种尝试通常都有O(k) 操作,其中k 是字符串的长度,以位为单位(或等效地以字符为单位)。

      这是假设你有一个好的散列函数。如果您不希望“农场”和“农场动物”散列到相同的值,那么散列函数将不得不使用密钥的所有位,因此散列“农场动物”大约需要两倍的时间“农场”(除非您处于某种滚动哈希场景中,但也有一些类似的操作保存场景与尝试)。使用香草树,很明显为什么插入“农场动物”所需的时间大约是“农场”的两倍。从长远来看,压缩尝试也是如此。

      【讨论】:

        【解决方案6】:

        在 trie 上的插入和查找与输入字符串 O(s) 的长度呈线性关系。

        散列会给你一个 O(1) 的查找和插入,但首先你必须根据输入字符串计算散列,这也是 O(s)。

        结论,渐近时间复杂度在这两种情况下都是线性的。

        从数据的角度来看,trie 有更多开销,但您可以选择一个压缩的 trie,它会让您再次或多或少地与哈希表平起平坐。

        要打破平局,请问自己这个问题:我只需要查找完整的单词吗?还是我需要返回与前缀匹配的所有单词? (如在预测文本输入系统中)。对于第一种情况,请使用哈希。它是更简单、更干净的代码。更容易测试和维护。对于前缀或后缀很重要的更详细的用例,请尝试一下。

        如果您只是为了好玩而这样做,那么实施 trie 可以很好地利用周日下午。

        【讨论】:

        • "散列会给你一个 O(1) 用于查找和插入,但首先你必须根据输入字符串计算散列,这也是 O(s)。感谢您解释这一点!
        • 计算哈希函数不是 O(s)。它实际上是 O(1)。您不需要字符串的所有位来计算它,其中一些(恒定数量)就足够了。
        【解决方案7】:
        与基本的 Trie 实现相比,

        HashTable 实现更节省空间。但是对于字符串,在大多数实际应用中都需要排序。但是 HashTable 完全打乱了字典顺序。现在,如果您的应用程序正在执行基于字典顺序的操作(如部分搜索、所有具有给定前缀的字符串、所有单词按排序顺序),您应该使用 Tries。仅用于查找,应使用 HashTable(可以说,它提供了最短的查找时间)。

        P.S.:除此之外,三元搜索树 (TST) 将是一个很好的选择。它的查找时间比 HashTable 长,但在所有其他操作中都具有时间效率。此外,它比尝试更节省空间。

        【讨论】:

          【解决方案8】:

          一些(通常是嵌入式的、实时的)应用程序要求处理时间独立于数据。在这种情况下,哈希表可以保证已知的执行时间,而 trie 会根据数据而变化。

          【讨论】:

          • 大多数哈希表不保证已知的执行时间——最坏的情况是 O(n),如果每个元素都发生冲突并被链接起来
          • 对于任何数据集,您都可以计算一个完美的散列函数,以保证对该数据的 O(1) 查找。当然,计算完美的哈希值并不是免费的。
          • 此外,链接并不是处理冲突的唯一方法;有各种有趣、聪明的方法来处理这个问题——其中一种是布谷鸟哈希 (en.wikipedia.org/wiki/Cuckoo_hashing)——最佳选择取决于客户端代码的需求。
          • 不知道 cuckoo hashing 及其与布隆过滤器的关系,这将是一个有趣的阅读,谢谢!
          • 不要忘记 Robin-hood Hashing,它在缓存和方差方面表现出色。 sebastiansylvan.com/2013/05/08/…codecapsule.com/2013/11/11/robin-hood-hashing
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-09-24
          • 2020-07-22
          • 2023-03-25
          • 2012-11-27
          • 2011-01-24
          • 1970-01-01
          相关资源
          最近更新 更多