哈希排序——Top-K算法

百度面试题: 面试

    搜索引擎会经过日志文件把用户每次检索使用的全部检索串都记录下来,每一个查询串的长度为1-255字节。 算法

    假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但若是除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G 数组

什么是哈希表? 数据结构

    哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它经过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫作散列函数,存放记录的数组叫作散列表。 函数

 

    哈希表的作法其实很简单,就是把Key经过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,而后就将该数字对数组长度进行取余,取余结果就看成数组的下标,将value存储在以该数字为下标的数组空间里。 优化

问题解析: 搜索引擎

此问题的解决分为如下俩个步骤: spa

第一步:统计每一个Query出现的次数。 日志

   Query统计有如下俩个方法,可供选择: 排序

    一、直接排序法

    首先咱们最早想到的的算法就是排序了,首先对这个日志里面的全部Query都进行排序,而后再遍历排好序的Query,统计每一个Query出现的次数了。

    可是题目中有明确要求,那就是内存不能超过1G,一千万条记录,每条记录是225Byte,很显然要占据2.55G内存,这个条件就不知足要求了。

 

   让咱们回忆一下数据结构课程上的内容,当数据量比较大并且内存没法装下的时候,咱们能够采用外排序的方法来进行排序,这里咱们能够采用归并排序,由于归并排序有一个比较好的时间复杂度O(NlgN)。

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

    综合分析一下,排序的时间复杂度是O(NlgN),而遍历的时间复杂度是O(N),所以该算法的整体时间复杂度就是O(N+NlgN)=O(NlgN)。

    二、Hash Table法

    在第1个方法中,咱们采用了排序的办法来统计每一个Query出现的次数,时间复杂度是NlgN,那么能不能有更好的方法来存储,而时间复杂度更低呢?

    题目中说明了,虽然有一千万个Query,可是因为重复度比较高,所以事实上只有300万的Query,每一个Query255Byte,所以咱们能够考虑把他们都放进内存中去,而如今只是须要一个合适的数据结构,在这里,Hash Table绝对是咱们优先的选择,由于Hash Table的查询速度很是的快,几乎是O(1)的时间复杂度。

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

    本方法相比算法1:在时间复杂度上提升了一个数量级,为O(N),但不只仅是时间复杂度上的优化,该方法只须要IO数据文件一次,而算法1的IO次数较多的,所以该算法2比算法1在工程上有更好的可操做性。

第二步:根据统计结果,找出Top 10

    算法一:普通排序

    我想对于排序算法你们都已经不陌生了,这里不在赘述,咱们要注意的是排序算法的时间复杂度是NlgN,在本题目中,三百万条记录,用1G内存是能够存下的。

    算法二:部分排序

    题目要求是求出Top 10,所以咱们没有必要对全部的Query都进行排序,咱们只须要维护一个10个大小的数组,初始化放入10个Query,按照每一个Query的统计次数由大到小排序,而后遍历这300万条记录,每读一条记录就和数组最后一个Query对比,若是小于这个Query,那么继续遍历,不然,将数组中最后一条数据淘汰,加入当前的Query。最后当全部的数据都遍历完毕以后,那么这个数组中的10个Query即是咱们要找的Top10了。

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

    算法三:堆

    在算法二中,咱们已经将时间复杂度由NlogN优化到NK,不得不说这是一个比较大的改进了,但是有没有更好的办法呢?

    分析一下,在算法二中,每次比较完成以后,须要的操做复杂度都是K,由于要把元素插入到一个线性表之中,并且采用的是顺序比较。这里咱们注意一下,该数组是有序的,一次咱们每次查找的时候能够采用二分的方法查找,这样操做的复杂度就降到了logK,但是,随之而来的问题就是数据移动,由于移动数据次数增多了。不过,这个算法仍是比算法二有了改进。

    基于以上的分析,咱们想一想,有没有一种既能快速查找,又能快速移动元素的数据结构呢?回答是确定的,那就是堆。

    借助堆结构,咱们能够在log量级的时间内查找和调整/移动。所以到这里,咱们的算法能够改进为这样,维护一个K(该题目中是10)大小的小根堆,而后遍历300万的Query,分别和根元素进行对比。

    思想与上述算法二一致,只是算法在算法三,咱们采用了最小堆这种数据结构代替数组,把查找目标元素的时间复杂度有O(K)降到了O(logK)。

    那么这样,采用堆数据结构,算法三,最终的时间复杂度就降到了N‘logK,和算法二相比,又有了比较大的改进。

总结:

    至此,算法就彻底结束了,通过上述第一步、先用Hash表统计每一个Query出现的次数,O(N);而后第二步、采用堆数据结构找出Top 10,N*O(logK)。因此,咱们最终的时间复杂度是:O(N) + N'*O(logK)。(N为1000万,N’为300万)。若是各位有什么更好的算法,欢迎留言评论。第一部分,完。

相关文章
相关标签/搜索