我正在为我正在从事的项目构建符号表。我想知道人们对于存储和创建符号表的各种方法的优缺点有何看法。
我已经做了相当多的搜索,最常用的建议是二叉树或链表或哈希表。以上所有优点和缺点是什么?(在C ++中工作)
Answers:
您的用例大概将是“一次插入数据(例如,应用程序启动),然后执行大量读取,但如果有任何额外的插入,则很少”。
因此,您需要使用一种快速查找所需信息的算法。
因此,我认为HashTable是最适合使用的算法,因为它只是生成关键对象的哈希,然后使用该哈希来访问目标数据-它是O(1)。其他的是O(N)(大小为N的链接列表-您必须一次遍历一个列表,平均重复N / 2次)和O(log N)(二叉树-您将搜索空间减半)每次迭代-仅在树是平衡的情况下,因此这取决于您的实现,不平衡的树的性能可能会大大降低)。
只需确保HashTable中有足够的空间(存储桶)来存储您的数据(Re,Soraz在此岗位上的评论)。大多数框架实现(Java,.NET等)的质量都不需要担心实现。
您是否在大学学习过有关数据结构和算法的课程?
这些数据结构之间的标准折衷适用。
似乎每个人都忘记的是,对于较小的Ns(即表中的符号很少),链表可以比哈希表快得多,尽管从理论上讲,其渐进复杂度确实更高。
从Pike的C语言编程笔记中可以找到一个著名的qoute:“规则3。当n很小时,fancy算法很慢,而n通常很小。fancy算法具有很大的常数。直到您知道n经常变得很大,不要幻想。” http://www.lysator.liu.se/c/pikestyle.html
我无法从您的帖子中得知您是否要处理小N,但是请始终记住,针对大N的最佳算法不一定适合于小N。
听起来以下事实可能都是正确的:
如果是这样,您可能会考虑这些其他结构中的任何一个排序列表。这在插入期间的性能会比其他方法差,因为插入时排序列表为O(N),而链表或哈希表的排序列表为O(1),而O(log 2N)表示平衡的二叉树。但是排序列表中的查找可能比其他任何结构都要快(我将在稍后对此进行解释),因此您可能会排名第一。另外,如果您一次执行所有插入操作(或者在完成所有插入操作之前不需要查找),则可以简化对O(1)的插入操作,并在最后进行一种更快的排序。而且,排序后的列表使用的内存比其他任何结构都要少,但这唯一可能的影响是如果您有很多小列表。如果您有一个或几个大列表,则哈希表的性能可能会超过排序列表。
为什么使用排序列表查找会更快?好吧,很明显,它比链表的O(N)查找时间要快。对于二叉树,如果树保持完美平衡,则查找仅保持O(log 2 N)。使树保持平衡(例如,红黑色)会增加复杂性和插入时间。此外,对于链表和二叉树,每个元素都是一个单独分配的1 节点,这意味着您必须取消引用指针,并且可能跳转到可能变化很大的内存地址,从而增加了发生高速缓存未命中的机会。
至于哈希表,您可能应该在此处阅读有关StackOverflow的其他几个问题,但这里的主要关注点是:
当然,如果您真的在乎这些数据结构将如何执行,则应该对其进行测试。对于大多数常见语言,找到其中任何一种的良好实现,应该几乎没有问题。在这些数据结构中的每个数据结构上抛出一些实际数据并查看哪种方法效果最好应该不太困难。
我喜欢比尔的答案,但它并不能真正综合事物。
从三个选择中:
链接列表从(O(n))查找项目相对较慢。因此,如果表中有很多项目,或者要进行很多查找,那么它们并不是最佳选择。但是,它们易于构建,也易于编写。如果表很小,并且/或者您在构建表之后只对它进行过一次小扫描,那么这可能是您的选择。
哈希表的速度非常快。但是,要使其正常工作,您必须为输入选择一个良好的哈希值,并且必须选择一个足够大的表以容纳所有内容而不会发生很多哈希冲突。这意味着您必须了解输入内容的大小和数量。如果搞砸了,最终会得到一组非常昂贵和复杂的链表。我想说的是,除非您提前知道表的大小,否则不要使用哈希表。这与您的“接受”答案不同。抱歉。
留下树木。但是,您可以在这里进行选择:平衡还是不平衡。通过在C和Fortran代码上研究此问题,我们发现这里的符号表输入往往具有足够的随机性,以至于由于不平衡树而只会损失一两棵树。鉴于平衡树插入元素的速度较慢且难以实现,因此我不会理会它们。但是,如果您已经可以访问漂亮的调试组件库(例如:C ++的STL),那么您不妨继续使用平衡树。
需要注意的几件事。
二叉树只有O(log n)查找,并且如果树是平衡的,则插入复杂度。如果您的符号以非常随机的方式插入,这应该不是问题。如果按顺序插入它们,则将建立一个链接列表。(对于您的特定应用程序,它们不应处于任何顺序,因此您应该可以。)如果符号可能太有序,则红黑树是更好的选择。
哈希表提供O(1)平均插入和查找复杂度,但是这里也有一个警告。如果您的哈希函数很糟糕(我的意思是真的很糟糕),那么您最终也可能会在这里建立一个链表。但是,任何合理的字符串哈希函数都应该执行此操作,因此,此警告实际上仅是确保您知道它可能发生。您应该能够测试您的散列函数在预期的输入范围内没有很多冲突,这样就可以了。另一个较小的缺点是,如果您使用的是固定大小的哈希表。大多数哈希表实现会在达到一定大小时增长(更精确地说是负载因子,请参见此处有关详细信息)。这是为了避免在向十个存储桶中插入一百万个符号时遇到的问题。这只会导致十个链接列表,平均大小为100,000。
如果我的符号表很短,我只会使用链表。这是最容易实现的,但是链表的最佳情况性能是其他两个选项的最差情况。
其他评论集中在添加/检索元素上,但是,如果不考虑迭代整个集合所需的时间,则本讨论是不完整的。简短的答案是,哈希表需要较少的内存来进行迭代,但是树所需的时间更少。
对于哈希表,遍历(键,值)对的内存开销不取决于表的容量或表中存储的元素数量;实际上,迭代只需要一个或两个索引变量。
对于树,所需的内存量始终取决于树的大小。您可以在迭代时维护未访问节点的队列,也可以添加指向树的其他指针以简化迭代(出于迭代目的,使树像链接列表一样工作),但是无论哪种方式,您都必须为迭代分配额外的内存。
但是,在计时方面,情况恰恰相反。对于哈希表,迭代所需的时间取决于表的容量,而不是存储元素的数量。因此,以10%的容量加载的表进行迭代所花费的时间将比具有相同元素的链表的访问时间长10倍!
这个问题通过C#中的不同容器进行,但是在您使用的任何语言中它们都是相似的。