TOP K问题

题目:搜索关键词的TOP K问题。

搜索引擎每天会把用户检索使用的关键词都记录下来,并保存到日志文件中,每个关键词的长度为1-255字节。假设目前有1000万个记录(这些关键词的重复度较高,虽然总数是1000万,但去重后不超过300万。一个关键词的重复度越高,说明检索它的用户越多,也就是越热门的关键词。)请你统计最热门的10个关键词,要求使用的内存不超过1G。

  • 解决这类问题,原则上分为两步:1统计出每个关键词被检索的次数;2找出检索次数最多的K个关键词。
  • 第一步:统计出每个关键词被检索的次数

最简单直接的解决方法就是直接排序法。将1000万条记录进行排序,然后扫描排好序的序列,统计出每个关键词出现的次数。题目中要求内存不能超过1G,每条记录占1-255字节,1000万条记录最大可占据 255*1000*10000/1024/1024/1024=2.375G内存,因此直接将1000万条记录读进内存并使用内部排序的方法是不行的。所以只能使用外部排序的方法,例如归并排序,排序算法的时间复杂度为O(NlgN),其中N为日志文件中记录的条数。排序后扫描一遍序列并统计出每个关键词出现的次数,这个过程的时间复杂度为O(N)。

综合考虑,使用“外部排序+依次扫描序列”的方法统计关键词出现次数的时间复杂度为O(N+NlgN)=O(NlgN)。

  • 可以将统计的结果保存在一个结构体数组里面。其中每个表项都是一个类似于下面定义的结构体变量。key保存关键词,value中保存该关键词在日志记录中出现的次数。由于去重后关键场次不超过300万个,所以这个表大小不会超过1G。
  • 以上这种统计关键词出现次数的方法虽然可行,但并不高效。可以通过构建一个hash表来实现。
  • 可以在内存中建立一个hash表,然后将日志中的记录逐一填入该hash表。这个hash表的结构类似于上述数组,每个表项包含一个key用来存放关键词,一个value用来存放该关键词出现的次数。不同的是该表在hash造表的过程中动态生成:如果hash表中没有这个记录就将这个关键词插入表中;如果存在这条记录就将这个关键词的个数加1,当扫描完1000万条日志记录后,hash表就建好了。该hash表有300万个关键词,占用内存大约730MB。
  • 在hash造表的过程中,每次向hash表中插入关键词的时间复杂度为O(1),因此利用hash表的方法统计出每个关键词被检索次数的时间复杂度为O(N),相比于外部排序方法的时间复杂度O(NlgN)提高了一个数量级。
  • 第二步:找出检索次数最多的K个关键词

最直接的方法就是将hash表按照value值从大到小排序,排序后的前K个表项中的关键词就是要找的TOP K关键词,但是这种方法的时间复杂度为O(NlogN),并且会有大量冗余操作,因为没有必要对所有关键词排序,这里只关心检索次数最多的前K个关键词。

还有一种更优化的方法,就是利用一个长度为K的数组存放TOP K的关键词,不妨称之为TOP K数组。初始状态时可将hash表中的前10个表项里的关键词存放到TOP K数组中,并按其value值的大小排序;然后遍历后续3000000-10=2999990条记录,每读一条记录就将该纪录的检索次数value和数组中最后一个关键词的value进行比较,如果小于这个关键词的value,那么继续遍历,否则将TOP K数组中最后一个关键词淘汰,并把这个新关键词加入到TOP K数组中,加入后数组依然按照关键词的value从大到小排序。最后当hash表中的所有数据都遍历完毕之后TOP K数组中的10个关键词便是要找的TOP10了。

  • 首先初始化TOP K数组时并不需要将里面的关键词按value值从大到小排序,而是可以将这些关键词按照value值的大小构成一个小顶堆。在将key插入TOP K数组时,就只需要一次时间复杂度为O(logK)的调整堆操作即可,而不需要再进行排序了。这样该算法的时间复杂度为O(N)+NO(logK)。
  • 综上所述,解决这类TOP K问题最推荐的方法是:使用hash表进行关键词频度的统计,使用局部小顶堆的方法进行TOP K元素的查找。按照这种方法求解TOP K问题的时间复杂度为O(N)+O(N)+N'O(logK),其中N为原始日志中记录的条数,本题N为1000万条,N'为hash表中记录的条数,本题为300万,K为TOP K问题中要求解的K值,本题为10。

你可能感兴趣的:(程序员面试笔记,大数据)