【发布时间】:2010-09-27 03:13:21
【问题描述】:
我正在为我正在进行的项目构建符号表。我想知道人们对存储和创建符号表的各种方法的优缺点有何看法。
我进行了相当多的搜索,最常推荐的是二叉树或链表或哈希表。以上所有的优点和缺点是什么? (在 C++ 中工作)
【问题讨论】:
标签: algorithm hashtable linked-list binary-tree symbol-tables
我正在为我正在进行的项目构建符号表。我想知道人们对存储和创建符号表的各种方法的优缺点有何看法。
我进行了相当多的搜索,最常推荐的是二叉树或链表或哈希表。以上所有的优点和缺点是什么? (在 C++ 中工作)
【问题讨论】:
标签: algorithm hashtable linked-list binary-tree symbol-tables
当然,这取决于几件事。我会说链接列表是正确的,因为它几乎没有合适的属性可以用作符号表。如果您已经拥有二叉树并且不必花时间编写和调试它,二叉树可能会起作用。我的选择是哈希表,我认为这或多或少是用于此目的的默认值。
【讨论】:
This question 在 C# 中遍历不同的容器,但在您使用的任何语言中它们都是相似的。
【讨论】:
这些数据结构之间的标准权衡适用。
【讨论】:
您的用例大概是“插入一次数据(例如,应用程序启动),然后执行大量读取,但如果有额外插入,则很少”。
因此,您需要使用一种能够快速查找所需信息的算法。
因此,我认为 HashTable 是最适合使用的算法,因为它只是生成密钥对象的哈希并使用它来访问目标数据 - 它是 O(1)。其他是 O(N) (大小为 N 的链接列表 - 您必须一次遍历一个列表,平均 N/2 次)和 O(log N) (二叉树 - 您将搜索空间减半每次迭代 - 仅当树是平衡的,所以这取决于您的实现,不平衡的树的性能可能会明显更差)。
只需确保 HashTable 中有足够的空间(存储桶)用于存储您的数据(例如,Soraz 对这篇文章的评论)。大多数框架实现(Java、.NET 等)都具有您无需担心实现的质量。
你在大学里修过数据结构和算法课程吗?
【讨论】:
除非你希望你的符号表很小,否则我应该避开链表。包含 1000 个项目的列表平均需要 500 次迭代才能找到其中的任何项目。
二叉树可以更快,只要它是平衡的。如果您要保留内容,则序列化表单可能会被排序,并且当它重新加载时,结果树将完全不平衡,并且它的行为与链表相同 - 因为那是基本上它变成了什么。平衡树算法解决了这个问题,但使整个 shebang 更加复杂。
哈希图(只要您选择合适的哈希算法)看起来是最好的解决方案。您没有提到您的环境,但几乎所有现代语言都内置了 Hashmap。
【讨论】:
有几点需要注意。
如果二叉树是平衡的,则二叉树只有 O(log n) 的查找和插入复杂度。如果您的符号以非常随机的方式插入,这应该不是问题。如果它们按顺序插入,您将构建一个链表。 (对于您的特定应用程序,它们不应该按任何顺序排列,所以应该没问题。)如果符号可能过于有序,Red-Black Tree 是更好的选择。
哈希表的平均插入和查找复杂度为 O(1),但这里也有一个警告。如果你的哈希函数很糟糕(我的意思是真的很糟糕),你最终也可以在这里建立一个链表。但是,任何合理的字符串散列函数都应该这样做,所以这个警告实际上只是为了确保您知道它可能会发生。您应该能够测试您的哈希函数在您的预期输入范围内没有很多冲突,您会没事的。另一个小缺点是如果您使用的是固定大小的哈希表。大多数哈希表实现在达到一定大小时会增长(更精确的负载因子,请参阅here 了解详细信息)。这是为了避免在将一百万个符号插入十个桶时遇到的问题。这只会导致十个平均大小为 100,000 的链表。
如果我有一个非常短的符号表,我只会使用链表。它最容易实现,但链表的最佳情况性能是其他两个选项的最差情况性能。
【讨论】:
大家似乎忘记了,对于小的 Ns,IE 表中很少的符号,链表可以比哈希表快得多,尽管理论上它的渐近复杂度确实更高。
Pike 的 C 语言编程笔记中有一句名言:“规则 3。当 n 小时,花哨的算法很慢,而 n 通常很小。花哨的算法有很大的常数。直到你知道 n 经常要大,不要花哨。” http://www.lysator.liu.se/c/pikestyle.html
我无法从您的帖子中判断您是否会处理小 N,但请始终记住,大 N 的最佳算法不一定适用于小 N。
【讨论】:
我喜欢比尔的回答,但它并没有真正综合起来。
从三个选项中:
链接列表从 (O(n)) 中查找项目相对较慢。因此,如果您的表中有 很多 项,或者您要进行大量查找,那么它们不是最佳选择。但是,它们很容易构建,也很容易编写。如果表很小,并且/或者您在构建后只对其进行一次小扫描,那么这可能是您的选择。
哈希表可以非常快。然而,为了让它工作,你必须为你的输入选择一个好的散列,并且你必须选择一个足够大的表来容纳所有东西,而不会出现很多散列冲突。这意味着您必须了解输入的大小和数量。如果你把它搞砸了,你最终会得到一组非常昂贵和复杂的链表。我会说,除非您提前知道表的大小,否则不要使用哈希表。这与您的“已接受”答案不一致。对不起。
留下树木。不过,您可以在这里选择:平衡或不平衡。通过研究我们这里的 C 和 Fortran 代码上的这个问题,我发现符号表输入往往是足够随机的,如果不平衡树,你只会损失大约一两个树级别。鉴于平衡树插入元素的速度较慢且难以实现,因此我不会打扰它们。但是,如果您已经可以访问很好的调试组件库(例如:C++ 的 STL),那么您不妨继续使用平衡树。
【讨论】:
听起来以下可能都是真的:
如果是这样,您可能会考虑在任何其他结构上使用排序列表。这在插入过程中会比其他的表现更差,因为排序列表在插入时是 O(N),而对于链表或哈希表是 O(1),而对于排序列表是 O(log2N)平衡二叉树。但是在排序列表中查找可能比任何其他结构都快(我将很快解释这一点),因此您可能会排在首位。此外,如果您一次执行所有插入(或者在所有插入完成之前不需要查找),那么您可以将插入简化为 O(1) 并在最后进行更快的排序。更重要的是,排序列表使用的内存比任何其他结构都少,但唯一可能重要的是如果您有许多小列表。如果您有一个或几个大列表,那么哈希表的性能可能会优于排序列表。
为什么使用排序列表查找会更快?好吧,很明显它比链表快,后者的查找时间为 O(N)。对于二叉树,如果树保持完美平衡,查找仅保持 O(log2 N)。保持树平衡(例如红黑)增加了复杂性和插入时间。此外,对于链表和二叉树,每个元素都是一个单独分配的1节点,这意味着您必须取消引用指针并可能跳转到潜在的广泛变化内存地址,增加缓存未命中的机会。
关于哈希表,您可能应该在 StackOverflow 上阅读 a couple 或 other questions,但这里的主要兴趣点是:
当然,如果您真的关心这些数据结构的性能,您应该测试它们。对于大多数常用语言,您应该可以轻松找到其中任何一个的良好实现。将您的一些真实数据放入这些数据结构中的每一个并查看哪个表现最佳应该不会太难。
【讨论】:
其他 cmets 专注于添加/检索元素,但如果不考虑迭代整个集合需要什么,这个讨论是不完整的。这里简短的回答是,哈希表需要更少的内存来迭代,但树需要更少的时间。
对于哈希表,迭代(键,值)对的内存开销不取决于表的容量或表中存储的元素数量;事实上,迭代应该只需要一个或两个索引变量。
对于树,所需的内存量始终取决于树的大小。您可以在迭代时维护未访问节点的队列,也可以向树添加额外的指针以便于迭代(为了迭代的目的,使树像链表一样),但无论哪种方式,您都必须为迭代分配额外的内存.
但在时机方面情况正好相反。对于哈希表,迭代所需的时间取决于表的容量,而不是存储元素的数量。因此,以 10% 的容量加载的表将比具有相同元素的链表花费大约 10 倍的时间来迭代!
【讨论】: