如何在哈希表和Trie(前缀树)之间进行选择?


134

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

一旦获得索引,哈希表将立即查找。但是,散列密钥以获取索引似乎很容易采取近50个步骤。

有人可以为此提供更丰富的见解吗?谢谢!


1
值得注意的是,redix树比普通树更有效,因为您不需要为每个字符串字节创建新分支。另外,redix树比散列表更好地支持“模糊”搜索,因为在路径上工作时您正在查看各个位。例如,00110010可能是输入字节,但您要包括00111010仅删除一位的匹配项。
Xeoncross

Answers:


116

尝试的优势:

基础:

  • 可预测的O(k)查找时间,其中k是密钥的大小
  • 如果不存在,查找时间可能少于k次
  • 支持有序遍历
  • 无需哈希函数
  • 删除很简单

新操作:

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

链接结构的优点:

  • 如果有许多公共前缀,则它们所需的空间将共享。
  • 不变的尝试可以共享结构。无需就地更新Trie,您可以构建一个新的,只沿一个分支不同,而其他分支指向旧的Trie。这对于并发,表的多个同时版本等很有用。
  • 不变的特里是可压缩的。也就是说,它也可以通过哈希约束共享后缀上的结构。

哈希表的优点:

  • 每个人都知道哈希表,对不对?您的系统已经有一个很好的优化实现,比大多数情况下要快。
  • 您的密钥不需要任何特殊的结构。
  • 比明显的链接特里结构更节省空间(请参阅下面的评论

26
不能完全同意“比明显的链接的trie结构更节省空间” —在一般的哈希表实现中,它包含更大的空间来包含键,而在尝试中,每个节点代表一个单词。从这个意义上讲,尝试更加节省空间。
galactica

1
从一个结构与另一个结构访问数据怎么样?我在考虑缓存和位置
Horia Toma 2014年

8
@galactica,这与我的经验相矛盾:例如,在我对空间进行测量的所有结构的此答案中,特里的表现最差。这是有道理的,因为指针远大于字节。是的,共享前缀有帮助,但必须克服很多开销才能达到奇偶校验。节省空间的表示形式可以有很大帮助,但是随后我们不再谈论明显的链接结构。
达里乌斯·培根

1
@DariusBacon处理电话号码计划似乎是尝试的合理方案。示例场景:电话号码与运营商的匹配,包括 从一个运营商移植到另一运营商的号码。对于通常的字典,它可能取决于语言(普通话对英语),您需要使用n-gram和/或其他统计数据。对于押韵书,后缀树似乎也是一个不错的选择。
mbx 2015年

查找数据的多样性非常重要。如果您的数据值中有很大一部分是唯一的,则由于使用了其他空指针,因此空间复杂度将超过散列值。
例如

45

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


8
如果哈希表和trie在查询上具有相同的复杂度,那么对于k个长度的字符串,O(k)为什么要进行哈希处理?你能解释一下吗?
Sazzad Hissain Khan

29

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

在大多数甚至很小的延迟/可扩展性(例如:高频交易)都需要解决的工业场景中,创建巨大的哈希表以进行有效查找并不是一个很好的解决方案。您还必须考虑要针对其在内存中占用的空间进行优化的数据结构,以减少缓存丢失。

消息中间件是一个很好的例子,其中trie更适合要求。您有一百万个消息订阅者和发布者,这些消息属于各种类别(以JMS术语表示-主题或交易所),在这种情况下,如果您要基于主题(实际上是字符串)过滤出消息,则绝对不希望创建哈希表有百万个主题的百万个订阅。更好的方法是将主题存储在trie中,因此,基于主题匹配进行过滤时,其复杂性与主题/订阅/发布者的数量无关(仅取决于字符串的长度)。我喜欢它,因为您可以利用这种数据结构来优化空间需求,从而降低缓存丢失率。


10

使用树:

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

4

我没有看到任何人明确提及某些事情,我谨记这一点很重要。哈希表和各种尝试都通常具有O(k)操作,其中k字符串的长度以位为单位(或等效地以chars为单位)。

这是假设您具有良好的哈希函数。如果您不希望“农场”和“农场动物”哈希值相同,则哈希函数将必须使用键的所有位,因此对“农场动物”进行哈希处理所需的时间大约是该时间的两倍。 “农场”(除非您处于某种滚动哈希方案中,但在尝试操作时也存在一些类似的节省操作的方案)。而且很明显,为什么要插入“农场动物”所需的时间是“农场”的两倍左右。从长远来看,压缩尝试也是如此。


3

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

哈希将为您提供O(1)用于查找和插入,但首先您必须根据输入字符串再次计算哈希,该字符串也是O(s)。

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

从数据的角度来看,该Trie有更多开销,但是您可以选择一个压缩的Trie,这将使您或多或少地再次与哈希表建立联系。

要打破领带,请问自己一个问题:我是否只需要查找完整的单词?还是我需要返回所有匹配前缀的单词?(如在预想输入法中一样)。对于第一种情况,请进行哈希处理。它是更简单,更简洁的代码。易于测试和维护。对于前缀或后缀很重要的更复杂的用例,请尝试一下。

而且,如果您只是为了好玩而做,则实施Trie将使周日的下午变得很有用。


“哈希将为您提供O(1)用于查找和插入,但首先您必须根据输入字符串再次计算O(s)来计算哈希。” 感谢您的解释!
abadawi

2

与基本Trie实现相比,HashTable实现节省了空间。但是对于字符串,在大多数实际应用中必须进行排序。但是HashTable完全扰乱了词典顺序。现在,如果您的应用程序是按字典顺序进行操作(例如部分搜索,所有具有给定前缀的字符串,所有单词均按排序顺序),则应使用Tries。对于仅查找,应使用HashTable(可以说,它提供了最少的查找时间)。

PS:除此之外,三元搜索树(TST)将是一个不错的选择。它的查找时间比HashTable多,但在所有其他操作中均节省时间。而且,它比尝试更节省空间。


-2

一些(通常是嵌入式,实时)应用程序要求处理时间与数据无关。在那种情况下,哈希表可以保证已知的执行时间,而字典则根据数据而变化。


6
大多数哈希表不能保证已知的执行时间-最坏的情况是O(n),如果每个元素发生冲突并被链接起来
Adam Rosenfield

2
对于任何数据集,您都可以计算出一个完美的哈希函数,该函数将保证对该数据进行O(1)查找。当然,计算完美的哈希值并不是免费的。
乔治五世·赖利

5
而且,链接不是处理冲突的唯一方法;有很多有趣的聪明方法可以解决这个问题,例如杜鹃哈希(en.wikipedia.org/wiki/Cuckoo_hashing),最好的选择取决于客户端代码的需求。
汉克·盖伊

不了解杜鹃哈希及其与Bloom过滤器的关系,这将使您读起来很有趣,谢谢!
Horia Toma 2014年

不要忘了Robin-hood Hashing,它在缓存和差异方面非常出色。sebastiansylvan.com/2013/05/08/... codecapsule.com/2013/11/11/robin-hood-hashing
震动李启
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.