二叉树与链接列表与哈希表

时间:2008-12-16 12:20:18

标签: algorithm hashtable linked-list binary-tree symbol-tables

我正在为我正在进行的项目构建一个符号表。我想知道人们对可用于存储和创建符号表的各种方法的优缺点有什么看法。

我做了很多搜索,最常推荐的是二叉树或链表或哈希表。以上所有优点和缺点是什么? (在c ++中工作)

10 个答案:

答案 0 :(得分:74)

这些数据结构之间的标准权衡适用。

  • 二叉树
    • 实施的中等复杂性(假设您无法从库中获取它们)
    • 插入是O(logN)
    • 查找是O(logN)
  • 链接列表(未排序)
    • 实施的复杂性低
    • 插入是O(1)
    • 查找是O(N)
  • 哈希表
    • 实施的高度复杂性
    • 插入平均为O(1)
    • 查找平均为O(1)

答案 1 :(得分:48)

您的用例可能是“一次插入数据(例如,应用程序启动),然后执行大量读取,但是如果有任何额外插入则很少”。

因此,您需要使用快速查找所需信息的算法。

因此,我认为HashTable是最合适的算法,因为它只是生成密钥对象的哈希并使用它来访问目标数据 - 它是O(1)。其他是O(N)(大小为N的链表 - 你必须一次遍历列表,平均N / 2次)和O(log N)(二进制树 - 你用搜索空间减半每次迭代 - 只有在树平衡的情况下,这取决于您的实现,不平衡的树可能会有明显更差的性能。)

确保HashTable中有足够的空间(存储桶)用于您的数据(R.e.,Soraz对此帖子的评论)。大多数框架实现(Java,.NET等)都具有质量,您无需担心实现。

您是否在大学开设了数据结构和算法课程?

答案 2 :(得分:42)

每个人似乎忘记的是,对于小N,IE表中的符号很少,链表可以比哈希表快得多,尽管理论上它的渐近复杂性确实更高。

Pike的C编程注释中有一个着名的qoute:“规则3.当n很小时,花式算法很慢,而n通常很小。花式算法有很大的常数。直到你知道n经常去要大,不要花哨。“ http://www.lysator.liu.se/c/pikestyle.html

我无法从你的帖子中看出你是否会处理小N,但是要记住,大N的最佳算法不一定对小N有好处。

答案 3 :(得分:8)

听起来以下情况可能都是真的:

  • 你的钥匙是字符串。
  • 插入一次。
  • 经常查看。
  • 键值对的数量相对较小(例如,小于K左右)。

如果是这样,您可以考虑对这些其他结构中的任何一个进行排序。在插入期间,这将比其他表现更差,因为排序列表在插入时为O(N),而对于链表或散列表为O(1),对于O(log 2 N)为平衡的二叉树。但是,排序列表中的查找可能比任何其他结构更快(我将在稍后解释),因此您可能会排在最前面。此外,如果您一次执行所有插入(或者在完成所有插入之前不需要查找),那么您可以简化插入到O(1)并在最后进行更快速的排序。更重要的是,排序列表比其他任何结构使用更少的内存,但这可能很重要的唯一方法是,如果你有许多小列表。如果您有一个或几个大型列表,那么哈希表可能会超出排序列表。

为什么使用排序列表查找会更快?嗯,很明显它比链表更快,后者的O(N)查询时间。对于二叉树,如果树保持完美平衡,则查找仅保留O(log 2 N)。保持树平衡(例如,红黑)会增加复杂性和插入时间。此外,对于链接列表和二叉树,每个元素都是一个单独分配的 1 节点,这意味着您必须取消引用指针并可能跳转到可能变化很大的内存地址,增加了缓存未命中的可能性。

至于哈希表,你应该在StackOverflow上阅读a couple other questions,但这里的主要观点是:

  • 在最坏的情况下,哈希表可以退化为O(N)。
  • 散列的成本非零,在某些实现中,它可能很重要,特别是在字符串的情况下。
  • 与链接列表和二叉树一样,每个条目都是节点,不仅存储键和值,在某些实现中也单独分配,因此您可以使用更多内存并增加缓存的机会未命中。

当然,如果您真的关心这些数据结构的执行方式,那么您应该对它们进行测试。对于大多数常见语言,您应该很难找到任何这些的良好实现。在每个数据结构中抛出一些真实数据并查看哪些数据结构表现最佳不应该太难。

  1. 实现可以预分配节点数组,这有助于解决缓存未命中问题。我没有在链接列表或二叉树的任何实际实现中看到这一点(当然不是我见过的每一个),尽管你可以自己推出。但是,由于节点对象必然大于键/值对,因此缓存未命中的可能性略高。

答案 4 :(得分:7)

我喜欢比尔的答案,但它并没有真正合成事物。

从三个选择中:

从(O(n))查找项目的链接列表相对较慢。因此,如果您的表中有 lot 项目,或者您将要进行大量查找,那么它们就不是最佳选择。但是,它们易于构建,并且易于编写。如果表格很小,并且/或者您在构建之后只进行了一次小扫描,那么这可能是您的选择。

哈希表可以非常快。但是,要使它工作,你必须为你的输入选择一个好的哈希,你必须选择一个足够大的表来保存所有内容而不会产生大量的哈希冲突。这意味着您必须了解输入的大小和数量。如果搞砸了,最终会得到一套非常昂贵且复杂的链表。我会说,除非你提前知道表格的大小,否则不要使用哈希表。这不同意你的“接受”答案。遗憾。

留下树木。你有一个选择:平衡或不平衡。通过在C和Fortran代码上研究这个问题我发现的是,符号表输入往往是随机的,你只能通过不平衡树而失去一两个树级别。鉴于平衡树插入元素的速度较慢且难以实现,我不打扰它们。但是,如果您已经可以访问不错的调试组件库(例如:C ++的STL),那么您可以继续使用平衡树。

答案 5 :(得分:6)

需要注意的几件事情。

  • 如果树平衡,二叉树只有O(log n)查找和插入复杂度。如果您的符号以非常随机的方式插入,这应该不是问题。如果它们按顺序插入,您将构建链接列表。 (对于您的特定应用,它们不应该是任何顺序,所以您应该没问题。)如果符号有可能过于有序,Red-Black树是更好的选择。

  • 哈希表给出了O(1)平均插入和查找复杂度,但这里也有一个警告。如果您的哈希函数不好(我的意思是真的坏),您最终也可以在此处构建链接列表。但是,任何合理的字符串哈希函数都应该这样做,所以这个警告实际上只是为了确保你知道它可能会发生。你应该能够测试你的哈希函数在你预期的输入范围内没有很多碰撞,你会没事的。另一个小缺点是,如果您使用固定大小的哈希表。大多数哈希表实现在达到特定大小时会增长(加载因子更精确,有关详细信息,请参阅here)。这是为了避免在将十亿个符号插入十个桶时遇到的问题。这只会导致十个链接列表,平均大小为100,000。

  • 如果我有一个非常短的符号表,我只会使用链表。这是最容易实现的,但链接列表的最佳案例性能是其他两个选项的最差情况。

答案 6 :(得分:1)

其他评论的重点是添加/检索元素,但如果不考虑迭代整个集合所需的内容,则此讨论不完整。这里简短的回答是哈希表需要更少的内存来迭代,但树需要的时间更少。

对于散列表,迭代(键,值)对的内存开销不依赖于表的容量或表中存储的元素数;事实上,迭代应该只需要一个或两个索引变量。

对于树,所需的内存量总是取决于树的大小。您可以在迭代时维护未访问节点的队列,也可以向树中添加其他指针以便于迭代(为了迭代,使树成为链接列表),但无论如何,您必须为迭代分配额外的内存

但在时间方面,情况正好相反。对于哈希表,迭代所需的时间取决于表的容量,而不是存储元素的数量。因此,以具有相同元素的链接列表,以10%的容量加载的表将比迭代过程花费大约10倍!

答案 7 :(得分:0)

当然,这取决于几件事。我会说链接列表是正确的,因为它没有适合作为符号表的属性。二叉树可能有效,如果您已经有一个并且不必花时间编写和调试它。我的选择是哈希表,我认为这或多或少是默认用途。

答案 8 :(得分:0)

This question遍历C#中的不同容器,但它们与您使用的任何语言类似。

答案 9 :(得分:0)

除非您希望您的符号表很小,否则我应该避开链接列表。 1000个项目的列表平均需要500次迭代才能找到其中的任何项目。

二叉树可以更快,只要它是平衡的。如果你持久化内容,序列化的表单可能会被排序,当它被重新加载时,结果树将完全不平衡,因此它的行为与链表相同 - 因为那是基本上它变成了什么。平衡树算法解决了这个问题,但使整个shebang变得更加复杂。

散列映射(只要您选择合适的散列算法)看起来就像是最佳解决方案。您没有提到您的环境,但几乎所有现代语言都内置了Hashmap。