Top K 问题

(转:http://www.cnblogs.com/lscheng/archive/2012/12/29/2838705.html)

问题描述:(百度面试题)搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为0-255字节。假设目前有1000万个记录,除去重复后,不超过300万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门,请你统计最热门的10个查询串,要求内存不能超过1G。

问题解析:

【分析】:要统计最热门查询,首先就是要统计每个query出现的次数,然后根据统计结果,找出Top 10。所以我们可以根据该思路分两步来设计该算法。

第一步:Query统计

   (算法一)直接排序法

    首先我们能想到的就是排序,首先对这个日志里面的所有Query进行排序,然后再遍历排好序的Query,统计每个Query出现的次数。但是题目中有明确要求,就是内存不超过1G,一千万条记录,每条记录是255字节,很显然要占据2.5G内存,这个条件就不满足要求了。

    让我们回忆下数据结构课程上的内容,当数据量较大而且内存无法装下的时候,我们可以采用外排序的方法来进行排序,这里我采用归并排序,因为归并排序有一个较好的时间复杂度O(nlogn)。

    排完序后,我们再对已有的Query文件进行遍历,统计每个Query出现的次数,再次写入文件中。

    综合分析一下,排序的时间复杂度是O(NlogN),而遍历的时间复杂度是O(N),因此该算法的总体时间复杂度是O(NlogN)。

    (算法二)Hash Table法

    题目说明了,虽然有1000万个Query,但是由于重复度比较高,因此事实上只有300万个Query,每个Query占255个字节,因此我们可以考虑把它们全部都放进内存去,Hash Table绝对是我们的优先选择,因为Hash Table的查询速度快,时间复杂度几乎是O(1)。

    那么我们的算法就有了:维护一个Key为Query的字串,Value为该Query出现次数的HashTable,每次读取一个Query,如果该字串不在Table中,那么加入该字串,并且将Value值设置为 1;如果该字串在Table中,那么将该字串的计数加1即可,最终我们在O(N)的时间复杂度完成了对该海量数据的处理。

第一步:找出Top 10

   (算法一)排序

    排除的时间复杂度是O(NlogN),在本题目中,三百万条记录,用1G内存是可以存下的。

   (算法二)部分排序

    题目要求的是求出Top 10,因此我们没有必要对所有的Query进行排序,我们只需要维护一个10个大小的数组,初始化放入10个Query,按照每个Query的统计次数由大到小排序,然后遍历这300万条记录,没读一条记录就与数组的最后一个Query进行对比,如果小于这个Query,则继续遍历,否则将数据的最后一条记录淘汰,加入当前的Query。最后当所以的数据都遍历完毕之后,那么这个数组中的10个Query便是我们要找的Top 10了。

    不难分析出,这样的算法的时间复杂度是N*K,K是top多少的值。

   (算法三)堆排序

    在算法二中,每次比较完成之后,需要操作的复杂度都是K,因为要把元素插入到一个线性表之中,而且采用的是顺序比较。这里我们注意下,该数组是有序的,因此我们每次查找的时候可以采用二分的方法查找,这样操作的复杂度就降低到了LogK,可是随之而来的就是数据移动,因此移动数据的次数增多了。借助堆结构,我们可以在Log量级的时间内查找和调整/移动。因此,我们的算法可以改进为这样,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行比对,那么时间复杂度就降低到了 NlogK。

原文地址:https://www.cnblogs.com/yongwangzhiqian/p/3957388.html