查找前10个搜索词的算法


115

我目前正在准备面试,它使我想起了一个在上次面试中曾经被问到的问题:

“已经要求您设计一些软件来连续显示Google上排名前10位的搜索词。您可以访问Feed,该Feed提供了当前在Google上搜索的无穷实时搜索词源。请描述哪种算法和数据结构您将用来实现此目的。您将设计两个变体:

(i)显示所有时间(即自您开始阅读提要以来)的前10个搜索词。

(ii)仅显示过去一个月的前10个搜索字词,每小时更新一次。

您可以使用一个近似值来获得前十名的列表,但您必须证明自己的选择是正确的。”
我在这次采访中大吃一惊,但实际上仍然不知道如何实现这一点。

第一部分要求在无限列表的不断增长的子序列中提供10个最频繁的项。我研究了选择算法,但找不到任何在线版本来解决此问题。

第二部分使用有限列表,但是由于要处理大量数据,您无法真正将整个月的搜索字词存储在内存中,也无法每小时计算一次直方图。

前十名列表不断更新的事实使问题变得更加棘手,因此您需要以某种方式在滑动窗口上计算前十名。

有任何想法吗?


11
@BlueRaja-这不是一个愚蠢的面试问题,对OP来说是一个错误的解释。它不是在无限列表中请求最频繁的项,而是在无限列表的有限子序列中请求最频繁的项。继续进行类比,what is the most frequent item in the subsequence [2; 2; 3; 3; 3; 4; 4; 4; 4; 5; 5] of your sequence?
IVlad

3
@BlueRaja-这当然是一个难题,但我不明白为什么如此愚蠢-似乎代表着拥有大量数据集的公司所面临的一个相当典型的问题。@IVlad-根据您的建议进行了修复,我的措辞不好!
del

Answers:


47

好吧,看起来数据量很大,存储所有频率的成本也许很高。当数据量太大以至于我们不能希望全部存储时,我们进入数据流算法的领域。

该领域的有用书籍: Muthukrishnan-“数据流:算法和应用程序”

我从上文中选择的与该问题密切相关的参考文献: Mokuani,Manku-“数据流上的近似频率计数” [pdf]

顺便说一下,斯坦福大学的Motwani(编辑)是一本非常重要的“随机化算法”书的作者。本书的第11章处理这个问题编辑:对不起,不好的参考,该特定的章节是在另一个问题上。经过检查后,我建议使用Muthukrishnan的书的5.1.2节(可在线获取)。

嘿,不错的面试问题。


2
+1非常有趣的内容,网站上应该有一种标记“阅读”内容的方法。感谢分享。
Ramadheer Singh'7

@Gollum:我的书签中有一个要阅读的文件夹;你可以那样做。我知道这些链接已添加到我的:)
凸轮

+1。流算法正是这里的主题,Muthu的书(迄今为止唯一写过的书,AFIAK)很棒。
ShreevatsaR

1
+1。相关:en.wikipedia.org/wiki/Online_algorithm。顺便说一句,莫特瓦尼(Motwani)最近去世了,所以也许一位作者更为准确。

很奇怪。我从书中认识了他,但是由于这个原因,他肯定一定更出名:“ Motwani是早期有影响力的PageRank算法论文的合著者之一(与Larry Page,Sergey Brin和Terry Winograd在一起),谷歌的搜索技术的基础“(en.wikipedia.org/wiki/Rajeev_Motwani
季米特里斯·ANDREOU

55

频率估算概述

有一些众所周知的算法可以使用固定的存储量为此类流提供频率估计。一个是“ 频繁”,作者是Misra和Gries(1982)。从n个项目的列表中,它使用k-1个计数器查找出现次数超过n / k次的所有项目。这是Boyer和Moore的多数算法(Fischer-Salzberg,1982)的推广,其中k2。Manku和Motwani的LossyCounting(2002)和Metwally的SpaceSaving(2005)算法具有相似的空间要求,但在某些情况下可以提供更准确的估计条件。

要记住的重要一点是,这些算法只能提供频率估计。具体来说,Misra-Gries估算值可能会使(n / k)个项目的实际频率低估。

假设您有一种算法,只有当某项出现的时间超过50%时,它才能肯定地识别该项。向该算法提供N个不同项的流,然后添加一个项x的另外N-1个副本,总共2N-1个项。如果算法告诉您x超过总数的50%,则它必须位于第一个流中;如果不是,则x不在初始流中。为了使算法能够确定,它必须存储初始流(或一些与其长度成比例的摘要)!因此,我们可以向自己证明,这种“精确”算法所需的空间为Ω(N)。

取而代之的是,这里描述的这些频率算法会提供一个估算值,识别出超过阈值的任何项目,以及一些低于阈值一定幅度的项目。例如,使用单个计数器的多数算法将始终给出结果。如果有任何项目超过流的50%,则会找到它。但这也可能会给您一个仅出现一次的项目。不对数据进行第二次传递就不会知道(再次使用单个计数器,而仅查找该项目)。

频繁算法

这是对Misra-Gries的Frequent算法的简单描述。Demaine(2002)和其他人对算法进行了优化,但这可以帮助您。

指定阈值分数1 / k;将发现出现次数超过n / k次的任何项目。创建一个空的地图(如一棵红黑树);键将是搜索字词,值将是该字词的计数器。

  1. 查看流中的每个项目。
  2. 如果该术语存在于地图中,请增加关联的计数器。
  3. 否则,如果地图少于k-1个条目,则将该术语添加到地图中,计数器为1。
  4. 但是,如果映射已经有k-1个条目,则在每个条目中递减计数器。如果在此过程中有任何计数器达到零,则将其从地图上删除。

请注意,您可以处理具有固定存储量的无限量数据(仅固定大小的映射)。所需的存储量仅取决于感兴趣的阈值,并且流的大小无关紧要。

计数搜索

在这种情况下,也许您缓冲了一个小时的搜索,然后对那个小时的数据执行此过程。如果您可以在本小时的搜索日志中进行第二遍检查,则可以准确获得第一遍检查中确定的“候选”的出现次数。或者,也许可以通过一次通行证,并报告所有应聘者,知道知道应该包括的任何物品,并且任何多余的杂物只会在下一个小时内消失,这也许是可以的。

任何确实超过兴趣阈值的候选人都将存储为摘要。保留这些摘要一个月的价值,每小时丢弃最古老的摘要,您将可以很好地近似最常见的搜索字词。


我相信该解决方案可以充当过滤器,从而减少您感兴趣的搜索词的数量。如果某个术语进入了地图,即使它不在地图中,也要开始跟踪其实际统计信息。然后,您可以跳过该数据的第二次,并产生排序从你收集的统计数据有限,前10名。
Dolph'7

我喜欢通过递减计数器从树上修剪搜索较少的术语的优雅方法。但是,一旦地图“满了”,是否就不需要为每个到达的新搜索词递减步骤?一旦这种情况开始发生,难道不会导致较新的搜索字词在计数器有足够的机会增加之前迅速从地图中删除的结果吗?
del

1
@del-请记住,此算法用于查找超过指定阈值频率的术语,而不必查找最常见的术语。如果最常用的术语低于指定的阈值,通常将找不到它们。您担心过快地删除较新的术语可能与这种情况有关。一种看待这种情况的方法是真正流行的“信号”,它们将在“噪音”中脱颖而出。但是有时候,找不到信号,只是随机搜索静态信号。
埃里克森(Erickson)2010年

@erickson-对-我要说的是,此算法的假设是,前10个字词在整个测量窗口中均匀分布。但是,只要您将测量窗口保持足够小(例如1小时),这可能是一个有效的假设。
删除

1
@erickson,虽然不要求均匀分布,但我想知道这在更现实的分布(幂律,Zipf)中如何工作。假设我们有N个不同的词,并保留容量为K的红黑树,希望它以K个最常用的词结尾。如果(N-K)个词的累计频率大于K个最常用词的累计频率,则最后的树将被保证包含垃圾。你同意吗?
Dimitris Andreou 2010年

19

这是我目前正在进行的研究项目之一。需求几乎完全符合您的要求,我们已经开发出了不错的算法来解决该问题。

输入

输入的是无休止的英语单词或短语(我们将其称为tokens)。

输出

  1. 到目前为止输出我们看到的前N个令牌(来自我们看到的所有令牌!)
  2. 在历史窗口(例如前一天或上周)中输出前N个令牌。

这项研究的应用是在Twitter或Facebook中找到热门话题或话题趋势。我们有一个在网站上进行爬网的爬网程序,该爬网程序生成单词流,这些单词流将输入到系统中。然后,系统将整体上或历史上输出最高频率的单词或短语。想象一下,在过去的几周中,“世界杯”一词在Twitter上出现了很多次。“章鱼保罗”也是如此。:)

串成整数

系统对每个单词都有一个整数ID。尽管Internet上几乎有无限可能的单词,但是在积累了大量单词之后,找到新单词的可能性越来越低。我们已经找到了400万个不同的单词,并为每个单词分配了唯一的ID。整个数据集可以作为哈希表加载到内存中,大约消耗300MB内存。(我们已经实现了自己的哈希表。Java的实现占用了大量内存开销)

然后,每个短语都可以标识为整数数组。

这很重要,因为对整数的排序和比较比对字符串的排序和比较快得多

存档数据

系统为每个令牌保留存档数据。基本上是成对的(Token, Frequency)。但是,存储数据的表非常庞大,以至于我们必须对表进行物理分区。一旦分区方案基于令牌的ngram。如果令牌是一个单词,则为1克。如果令牌是两个单词的短语,则为2gram。这样下去。大约4克时,我们有10亿条记录,表大小约为60GB。

处理传入流

系统将吸收传入的句子,直到内存被充分利用为止(是的,我们需要一个MemoryManager)。在提取了N个句子并将其存储在内存中之后,系统会暂停,并开始将每个句子标记为单词和短语。每个标记(单词或短语)都会被计数。

对于频繁使用的令牌,它们始终保留在内存中。对于不经常使用的令牌,它们将根据ID进行排序(请记住,我们将String转换为整数数组),然后序列化为磁盘文件。

(但是,对于您的问题,由于您仅在计算单词,因此您只能将所有单词-频率映射表都放在内存中。经过精心设计的数据结构将仅消耗300MB的内存来存储400万个单词。一些提示:使用ASCII字符可以代表字符串),这是可以接受的。

同时,一旦找到系统生成的任何磁盘文件,然后将其合并,将激活另一个进程。由于磁盘文件已排序,因此合并将采用类似合并排序的类似过程。由于我们要避免过多的随机磁盘搜寻,因此在这里也需要注意一些设计。这样做的目的是避免同时读取(合并进程)/写入(系统输出),并让合并进程从一个磁盘读取,同时写入另一个磁盘。这类似于实现锁定。

一天的结束

一天结束时,系统将在内存中存储很多频繁使用的令牌,并且频率存储在其他磁盘文件中(并且对每个文件进行了排序),其他许多不经常使用的令牌也存储在磁盘文件中。

系统将内存映射刷新到磁盘文件中(对它进行排序)。现在,问题就变成了合并一组已排序的磁盘文件。使用类似的过程,我们最终将得到一个排序后的磁盘文件。

然后,最后的任务是将排序后的磁盘文件合并到存档数据库中。取决于存档数据库的大小,如果足够大,该算法的工作原理如下:

   for each record in sorted disk file
        update archive database by increasing frequency
        if rowcount == 0 then put the record into a list
   end for

   for each record in the list of having rowcount == 0
        insert into archive database
   end for

直觉是,一段时间后,插入次数会越来越少。越来越多的操作将仅在更新中进行。并且此更新不会受到索引的惩罚。

希望整个解释对您有所帮助。:)


我不明白 一个单词的整数ID可以做什么样的有意义的排序或比较?这些数字不是随意的吗?
Dimitris Andreou 2010年

另外,计算单词的频率是Google MapReduce论文(labs.google.com/papers/mapreduce.html)中的第一个示例,它可以在几行中按比例缩放。您甚至可以将数据移动到google app angine中,然后执行这样的MapReduce(code.google.com/p/appengine-mapreduce
Dimitris Andreou 2010年

@Dimitris Andreou:对整数进行排序会更快。这是因为比较两个整数比比较两个字符串要快。
Silent SoNG,2010年

@Dimitris Andreou:Google的mapreduce是解决此问题的一种不错的分布式方法。啊! 感谢您提供链接。是的,这对我们使用多台计算机进行排序将是一件好事。好的方法。
SiLent SoNG,2010年

@Dimitris Andreou:到目前为止,我只在考虑单机排序方法。在分发中排序是个好主意。
SiLent SoNG,2010年

4

您可以将哈希表二进制搜索树结合使用。实施<search term, count>字典,告诉您每个搜索词已搜索了多少次。

显然,每小时都对整个哈希表进行迭代以获得前十名是非常糟糕的。但这是我们正在谈论的Google,因此您可以假设前十名都将获得,例如超过10,000次点击(尽管可能更大)。因此,每当搜索词的数量超过10000时,请将其插入BST。然后,每小时仅需从BST中获取前10个,则其中应包含相对较少的条目。

这解决了历史上排名前10位的问题。


真正棘手的部分是处理一个词在月度报告中的位置(例如,“堆栈溢出”在过去两个月中可能有5万次点击,而在过去一个月中只有10000次,而“亚马逊”可能有40次点击)过去两个月为000,最后一个月为30000。您希望月报表中的“堆栈溢出”之前出现“ amazon”)。为此,我将为所有主要(超过10000次历史搜索)搜索词存储30天的列表,该列表将告诉您每天搜索该词的次数。该列表就像FIFO队列一样工作:您删除第一天,然后每天(或每小时)插入一个新的一天,但是随后可能需要存储更多信息,这意味着更多的内存/空间。如果内存不是问题,请执行它,否则去那个“近似值”

这看起来是一个好的开始。然后,您可以担心删除具有10,000多个匹配但长期以来没有太多匹配的术语。


3

情况一)

维护所有搜索词的哈希表,以及与哈希表分开的排序前十位列表。每当进行搜索时,都增加哈希表中的相应项目,并检查该项目是否应该与前十名中的第10个项目一起切换。

O(1)查找前十个列表,并将最大O(log(n))插入哈希表(假设冲突由自平衡二叉树管理)。

情况ii) 我们维护一个哈希表和所有项目的排序列表,而不是维护一个巨大的哈希表和一个小的列表。无论何时进行搜索,该术语在哈希表中都会递增,并且在排序列表中,可以检查该术语以查看其是否应与其后的术语一起转换。自平衡二叉树可以很好地解决此问题,因为我们还需要能够快速查询它(稍后会对此进行更多介绍)。

此外,我们还以FIFO列表(队列)的形式维护“小时”列表。每个“小时”元素将包含在该特定小时内完成的所有搜索的列表。因此,例如,我们的小时列表可能如下所示:

Time: 0 hours
      -Search Terms:
          -free stuff: 56
          -funny pics: 321
          -stackoverflow: 1234
Time: 1 hour
      -Search Terms:
          -ebay: 12
          -funny pics: 1
          -stackoverflow: 522
          -BP sucks: 92

然后,每小时:如果列表中至少有720小时(即30天内的小时数),请查看列表中的第一个元素,并针对每个搜索字词,将哈希表中的该元素减少适当的数量。然后,从列表中删除第一个小时元素。

假设我们现在是721小时,我们已经准备好查看列表中的第一小时(以上)。我们将哈希表中的免费内容减少56,将有趣的图片减少321,依此类推,然后将小时0从列表中完全删除,因为我们将不再需要再次查看它。

之所以要保留所有可以快速查询的术语的排序列表,是因为在我们从720小时之前浏览搜索词之后的每一小时,我们需要确保前十名的列表保持排序。因此,例如,当我们在哈希表中将“免费内容”递减56时,我们将检查它现在在列表中的位置。因为它是一个自平衡的二叉树,所以所有这些都可以在O(log(n))时间内很好地完成。


编辑:牺牲空间的准确性...

与第二个清单一样,在第一个清单中也实现一个大清单可能会很有用。然后,我们可以在两种情况下应用以下空间优化:运行cron作业以删除列表中除顶部x项之外的所有项。这样可以减少空间需求(结果是使列表上的查询更快)。当然,这将导致近似结果,但这是允许的。可以在部署应用程序之前根据可用内存计算x,并在有更多可用内存时动态调整x


2

粗略的思考...

一直排名前十

  • 使用存储每个术语的计数的哈希集合(对术语进行消毒等)
  • 一个排序的数组,其中包含进行中的前10个,每当一项的计数等于或大于该数组中的最小计数时,就会在该阵列中添加一个术语/计数

对于每月每小时更新的前10名:

  • 使用以从开始模数744(一个月中的小时数)以来经过的小时数为索引的数组,该数组条目由哈希集合组成,在该哈希表中存储该小时段遇到的每个术语的计数。每当小时槽计数器更改时,条目就会重置
  • 只要当前时槽计数器发生变化(最多一次一个小时),就需要通过复制和展平该在时槽上索引的数组的内容来收集在时槽上索引的数组中的统计信息

Errr ...有意义吗?我没有像现实生活中那样想到

嗯,是的,忘了提了,每月统计信息所需的每小时“复制/展平”实际上可以重用所有时间前10名中使用的相同代码,这是一个很好的副作用。


2

确切的解决方案

首先,一种解决方案可以保证正确的结果,但是需要大量的内存(一张大地图)。

“全时”变体

维护一个以查询为键并将其计数为值的哈希图。此外,保留一个列表,列出到目前为止的10个最频繁查询以及第10个最频繁查询的计数(阈值)。

在读取查询流时不断更新地图。每次计数超过当前阈值时,请执行以下操作:从“前10个”列表中删除第10个查询,将其替换为刚刚更新的查询,并同时更新阈值。

“过去一个月”变体

保留相同的“前10名”列表,并按上述相同方式进行更新。同样,保持类似的映射,但是这次将30 * 24 = 720个计数(每小时一个)的向量存储为值。每小时对每个键执行以下操作:从向量中删除最早的计数器,最后添加一个新的计数器(初始化为0)。如果向量为全零,则从地图中删除键。另外,您每个小时都必须从头开始计算“前10名”列表。

注意:是的,这个时候我们存储720点的整数,而不是一个,但也有少得多键(全时变有尾部长)。

近似值

这些近似值不能保证正确的解决方案,但会减少内存消耗。

  1. 处理第N个查询,跳过其余查询。
  2. (仅适用于所有变体)在地图中最多保留M个键值对(M应该尽可能地大)。这是一种LRU缓存:每当您读取不在地图中的查询时,请删除计数为1的最近最少使用的查询,并将其替换为当前处理的查询。

我喜欢近似1中的概率方法。但是,使用近似2(LRU缓存),如果最初不太流行的术语后来变得流行,会发生什么情况?因为它们的数量很少,难道不是每次添加它们都会被丢弃吗?
del

@del是的,第二个近似值仅适用于某些查询流。它的可靠性较差,但同时需要较少的资源。注意:您也可以将两个近似值组合在一起。
Bolo 2010年

2

过去一个月的十大搜索词

使用存储器有效的索引/数据结构,诸如紧凑尝试(来自维基百科上的条目的尝试)大致限定存储器要求和n之间有一定的关系-许多术语。

如果有所需的内存可用(假设1),则可以保留确切的每月统计信息,并将其每月汇总到所有时间统计信息中。

这里也有一个假设将“上个月”解释为固定窗口。但是,即使每月窗口滑动,上述过程也显示了原理(可以使用给定大小的固定窗口来近似滑动)。

这让我想起了循环数据库,除了一些统计信息是“全天候”计算的(从某种意义上说,并不是所有数据都会保留; rrd通过平均,求和或选择最大/最小值来合并不考虑细节的时间段,在给定的任务中,丢失的细节是有关低频项的信息,这可能会引入错误。

假设1

如果我们不能保持整个月的理想状态,那么我们应该能够找到一定的期间P,而我们应该能够保持理想状态。例如,假设我们对某个时间段P拥有完善的统计数据,该时间段进入了n个月。
完善的统计定义功能f(search_term) -> search_term_occurance

如果我们可以将所有n完善的统计信息表保留在内存中,则可以按以下方式计算每月的滑动统计信息:

  • 添加最新期间的统计信息
  • 删除最旧的统计信息(因此我们必须保留n完善的统计信息表)

但是,如果我们仅将汇总级别(每月)保持在前10位,那么我们将能够从固定时间段的完整统计信息中丢弃大量数据。这已经给出了具有固定的存储过程要求的工作程序(假定完美的统计表的上限为周期P)。

上面过程的问题在于,如果我们仅将信息放在滑动窗口的前10个字词上(所有时间都类似),则对于一段时间内达到峰值的搜索字词,统计信息将是正确的,但可能看不到搜寻字词的统计资料会随着时间的流逝而不断增加。

可以通过将信息保留在前10个词(例如,前100个词)上,以希望前10个词是正确的,来抵消此信息。

我认为,进一步的分析可以将条目成为统计信息的一部分所需的最少出现次数关联起来(这与最大错误有关)。

(在决定哪些条目应成为统计信息的一部分时,您还可以监视和跟踪趋势;例如,如果对每个术语P在每个期间P内的出现情况进行线性外推可以告诉您该术语在一个月或两个月内将变得重要,可能已经开始对其进行跟踪。类似的原理适用于从跟踪池中删除搜索字词。)

上述情况最糟糕的情况是,当您有很多几乎相同频率的术语并且它们一直在变化时(例如,如果仅跟踪100个术语,那么如果前150个术语的出现频率相同,但是前50个术语在第一个月的出现频率更高,以免经常过一会儿就无法正确保存统计信息)。

也可能存在另一种在内存大小上不固定的方法(严格来说,上述两种方法都不是),该方法将根据发生次数/时间段(日,月,年,全时)定义最小重要性,以保持最小重要性。统计资料。这可以保证聚合期间每个统计信息的最大误差(再次参见循环法)。


2

修改“时钟页面替换算法”怎么样?(又称为“第二次机会”)怎么样?我可以想象,如果搜索请求均匀分布(这意味着大多数搜索到的词要定期出现,而不是连续出现5mio次,然后再也不会出现),则效果很好。

这是算法的直观表示: 时钟页面替换算法


0

将搜索字词的数量存储在一个巨大的哈希表中,在每个哈希表中,每个新搜索都会导致特定元素增加一个。跟踪前20个左右的搜索字词;当第11位的元素递增时,检查它是否需要与#10 *交换位置(不必保持前10位排序;您只关心在第10位和第11位之间进行区分)。

* 需要进行类似的检查,以查看新搜索词是否排在第11位,因此该算法也适用于其他搜索词-因此,我在简化一下。


您需要限制哈希表的大小。如果您获得了一系列独特的搜索结果怎么办?您需要确保您不会阻止自己注意到经常但不经常搜索的术语。随着时间的流逝,这可能是最热门的搜索字词,尤其是如果所有其他搜索字词都是“当前事件”,即现在搜索了很多,但下周搜索量就不那么多了。实际上,此类考虑可能是您想要做出的近似值。通过说出理由,我们不会捕获这类事情,因为这样做会使算法花费更多的时间/空间。
cape1232'7

我很确定Google可以对所有内容进行计数-有些计数不是静态保持的,而是根据需要进行计算的。
以太2010年

0

有时最好的答案是“我不知道”。

生病了。我的第一个直觉是将结果输入到Q中。一个过程将不断处理进入Q中的项目。该过程将维护一个

期限->计数

每次处理Q项目时,您只需查找搜索词并增加计数。

同时,我将维护对地图中前10个条目的引用列表。

对于当前已实现的条目,请查看其计数是否大于前10位中最小条目的计数(如果尚未在列表中)。如果是这样,请用条目替换最小的条目。

我认为那行得通。没有任何操作会占用大量时间。您将必须找到一种方法来管理计数图的大小。但这对于面试答案就足够了。

他们不希望找到解决方案,而是想看看您是否可以考虑。您不必再在那里写解决方案...。


12
数据结构称为a queueQ是一个字母:)。
IVlad

3
如果我要进行面试,“我不知道<stop>”绝对不是最好的答案。想想你的脚。如果您不知道,请弄清楚-或至少尝试一下。
斯蒂芬

在访问中,当我看到某人在其7页上处于休眠状态时,恢复了5次,而他们无法告诉我什么是ORM,我立即结束访问。我宁愿他们不把它放在简历上,而只是说:“我不知道”。没有人知道一切。@IVIad,我假装自己是C开发人员,并尝试保存位...;)
hvgotcodes 2010年

0

一种方法是,对于每次搜索,您都存储该搜索词及其时间戳。这样,在任何时间段内查找前十名仅是比较给定时间段内所有搜索词的问题。

该算法很简单,但是缺点是会增加内存和时间消耗。


0

如何使用具有10个节点的Splay树?每次您尝试访问树中未包含的值(搜索词)时,请丢掉任何叶子,而是插入该值并进行访问。

其背后的想法与我的其他答案相同。在均匀/定期访问搜索词的假设下,该解决方案应能很好地执行。

编辑

为了不删除可能很快再次访问的节点,还可以在树中存储更多搜索词(我在其他答案中建议的解决方案也是如此)。一个值存储在其中越多,结果越好。


0

不知道我是否正确。我的解决方案是使用堆。由于前10个搜索项的原因,我建立了一个大小为10的堆。然后用新的搜索更新该堆。如果新搜索的频率大于顶部堆(最大堆),请对其进行更新。放弃频率最小的那一个。

但是,如何计算特定搜索的频率将取决于其他方面。也许正如大家所说,数据流算法...。


0

使用cm-sketch存储自开始以来所有搜索的计数,将最小堆大小保持为10,排在前10位。对于每月结果,保持30 cm-sketch /哈希表和最小堆,每次开始从最近30,29 ..,1天开始计数和更新。作为一日通行证,清除最后一个并将其用作第一天。每小时的结果相同,保留60个哈希表和min-heap,然后开始计数最后60、59,... 1分钟。作为一分钟的传递,清除最后一个并用作第一分钟。

每月结果在1天范围内准确,每小时结果在1分钟范围内准确


0

当您有固定数量的内存和“无限”(认为非常大)令牌流时,此问题无法普遍解决。

粗略的解释...

要了解原因,请考虑在输入流中每N个令牌都有一个特定令牌(即单词)的令牌流。

另外,假设存储器最多可以保存对M个令牌的引用(字ID和计数)。

在这些条件下,有可能构建一个输入流,如果N足够大,则永远不会检测到令牌T,以使该流在T之间包含不同的M个令牌。

这与前N个算法的细节无关。它仅取决于极限M。

要了解为什么如此,请考虑由两个相同令牌组成的传入流:

T a1 a2 a3 ... a-M T b1 b2 b3 ... b-M ...

其中a和b都是不等于T的有效令牌。

请注意,在此流中,T对于ai和bi两次出现。但是,它似乎很少能从系统中清除。

从空的内存开始,第一个令牌(T)将占用内存中的一个插槽(以M为界)。然后,当M用尽时,a1将占用一个插槽,一直到a-(M-1)。

当aM到达时,算法必须丢弃一个符号,因此将其设为T。下一个符号将是b-1,这将导致a-1被刷新,依此类推。

因此,T将不会在内存中停留足够长的时间以建立实际计数。简而言之,任何算法都将丢失足够低的本地频率但较高的全局频率(在流的长度上)的令牌。

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.