【问题标题】:Finding k most common words in a file - memory usage在文件中查找 k 个最常用的单词 - 内存使用情况
【发布时间】:2012-12-21 09:59:36
【问题描述】:

假设您有一个很大的文件,比如 1GB。该文件每行包含一个单词(共 n 个单词),您要查找文件中出现频率最高的 k 个词。

现在,假设您有足够的内存来存储这些单词,那么在减少内存使用和 Big-O 复杂性中的持续开销方面,解决这个问题的更好方法是什么?我相信可以使用两种基本算法:

  1. 使用哈希表和最小堆来存储出现次数和看到的前 K 个单词。这是 O(n + nlogk) ~ O(N)
  2. 使用 trie 存储单词和出现次数,然后遍历 trie 以计算最常见的单词。这是 O(n*p) ~ O(N),其中 p 是最长单词的长度。

哪种方法更好?

另外:如果您没有足够的内存用于哈希表/trie(即 10MB 左右的有限内存),那么最好的方法是什么?

【问题讨论】:

  • 您希望在 1GB 文件中大约有多少个不同的词?
  • 我并没有特别期待什么。这个问题可以用现实世界的术语重写为从搜索列表或类似的东西中找到前 10 个搜索词,所以我猜它会遵循某种概率分布,但我没有设置特定的一个。

标签: algorithm data-structures


【解决方案1】:

关于常数更有效的是非常依赖。一方面,trie 为插入所有元素提供了严格的 O(N) 时间复杂度,而在最坏的情况下,哈希表可能会衰减到二次时间
另一方面,对于cache尝试效率并不高——每次搜索都需要O(|S|) 随机访问内存请求,这可能会导致性能显着衰减。

这两种方法都有效,我认为在选择其中一种方法时应考虑多种因素,例如最大 latency(如果是实时系统)、吞吐量和开发时间。

如果平均案例性能很重要,我建议生成一堆文件并运行 statistical analysis 哪种方法更好。 Wilcoxon 签名测试是实际使用的最先进的假设测试。


关于嵌入式系统:这两种方法仍然有效,但在这里: trie 中的每个“节点”(或一堆节点)都将在磁盘上而不是在 RAM 上。请注意,这意味着 trie O(|S|) 随机访问磁盘搜索每个条目,这可能会很慢。

对于哈希解决方案,您有 10MB,假设他们可以将其中的 5MB 用于磁盘指针的哈希表。我们还假设你可以在这 5MB 上存储 500 个不同的磁盘地址(这里悲观分析),这意味着在每次哈希查找后你还有 5MB 可以加载一个桶,如果你有 500 个桶,加载因子为 0.5,这意味着您可以存储 500 * 5MB * 0.5 ~= 1.25GB > 1GB 的数据,因此使用哈希表解决方案,因此使用哈希 - 每次查找只需要 O(1) 随机 磁盘查找 以便找到包含相关字符串的存储桶。

请注意,如果仍然不够,我们可以重新哈希指针表,非常类似于在虚拟内存机制中的paging table 中所做的。

由此我们可以得出结论,对于嵌入式系统,哈希解决方案在大多数情况下更好(请注意,在最坏的情况下,它可能仍会受到高延迟的影响,这里没有灵丹妙药)。


PS,radix tree 通常比 trie 更快、更紧凑,但与哈希表相比,trie 具有相同的副作用(当然,虽然不那么重要)。

【讨论】:

  • 所以基本上在无限内存的情况下,你说trie vs hash的选择取决于大小写?如果是这样,什么情况会使哪种数据结构更好?在第二种情况下,有没有比 trie 或 hash 更好的方法来解决问题?
  • @user1921187:以下是一些示例:例如,如果您的系统具有非常差的哈希机制,或者根本没有缓存 - 尝试的“缺点”不再相关 - 使用它。其他示例 - 如果您对每个查询有严格的时间限制 - 您无法承受哈希解决方案衰减到二次时间的低概率,您可能会选择 trie,即使它在平均情况下较慢。此外,尝试提供哈希表不提供的东西 - 排序。如果需要,您可以轻松地按顺序迭代尝试,并且尝试使用前缀搜索也很容易,但我认为这不是问题。
  • @user1921187:关于第二种情况(嵌入式系统) - 替代方案是排序和迭代。但是,它通常需要更多的磁盘寻道(我认为 ~*2 更多的磁盘寻道,但我可能是错的,如果这是一个问题,我可以稍后进行数学计算)然后是散列解决方案。由于在这种情况下磁盘 IO 是瓶颈,这意味着排序和迭代将消耗 ~*2 更多时间
【解决方案2】:

对于有限的内存选项,您可以先快速对列表进行排序,然后简单地填充一个包含 k 个项目的哈希表。然后,您将需要一个计数器来知道您正在检查的当前单词中有多少项目 - 如果它更高,那么您将哈希表中的最低项目替换为当前项目。

这对于初始列表可能没问题,但比仅扫描完整列表并使用计数填充哈希表要慢。

【讨论】:

  • 为什么要冒泡排序?使用 Quicksort 进行某种外部排序不是更有效吗?
  • 是的,我的错误 - 应该是快速排序!首先排序意味着您不必维护带有计数的单词列表 - 如果每个单词都是唯一的,这可能会使内存加倍,排序将其降低到 n+k。
  • 内存有限的快速排序很糟糕(请记住,您不能将文件存储在内存中)。如果有的话,您应该使用外部排序(通常是合并排序的一种变体)。但是,很少这样做——在磁盘上散列数据通常效率更高,并且需要更少的磁盘寻道
  • 是的,我的意思是合并,我的错误
【解决方案3】:

您是否开车存储中间结果?如果为真:

你可能有一些元结构。 和一组哈希表。 您读取了一部分数据(当您的哈希大小 3mb 你保存在磁盘上。 如果您限制为 10 mb,则哈希表的大小为 3 mb(例如)。

元描述你的哈希表。 在元中,您可以存储此哈希中唯一单词的数量和所有单词的数量以及一个世界的最大数量!!!我

之后。 您可以从磁盘加载哈希表并合并。

例如,您可以按唯一单词的升序或哈希中一个世界的最大计数加载哈希表。 在这一步中,您可以使用一些启发式方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-27
    相关资源
    最近更新 更多