使用堆实现Top K算法(JS实现)


Posted in Javascript onDecember 25, 2015

先来聊一聊Top K算法,具体内容如下

应用场景:

        搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。
        假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G。

必备知识:
什么是哈希表?
        哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。

        也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

哈希表的做法其实很简单,就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。
       而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位。
问题解析:

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

即,此问题的解决分为以下俩个步骤:

第一步:Query统计  (统计出每个Query出现的次数)
Query统计有以下俩个方法,可供选择:
        1)、直接排序法  (经常在日志文件中统计时,使用cat file|format key|sort | uniq -c | sort -nr | head -n 10,就是这种方法)
首先我们最先想到的的算法就是排序了,首先对这个日志里面的所有Query都进行排序,然后再遍历排好序的Query,统计每个Query出现的次数了。

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

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

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

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

       2)、Hash Table法 (这种方法统计字符串出现的次数非常好)
       在第1个方法中,我们采用了排序的办法来统计每个Query出现的次数,时间复杂度是NlgN,那么能不能有更好的方法来存储,而时间复杂度更低呢?

       题目中说明了,虽然有一千万个Query,但是由于重复度比较高,因此事实上只有300万的Query,每个Query 255Byte,因此我们可以考虑把他们都放进内存中去,而现在只是需要一个合适的数据结构,在这里,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(找出出现次数最多的10个)
算法一:普通排序(我们只用找出top10,所以全部排序有冗余)
     我想对于排序算法大家都已经不陌生了,这里不在赘述,我们要注意的是排序算法的时间复杂度是NlgN,在本题目中,三百万条记录,用1G内存是可以存下的。

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

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

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

       分析一下,在算法二中,每次比较完成之后,需要的操作复杂度都是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万)。

js如何使用堆实现Top K 算法?

1. 使用堆算法实现Top,时间复杂度为 O(LogN)

function top(arr,comp){ 
if(arr.length == 0){return ;} 
var i = arr.length / 2 | 0 ; 
for(;i >= 0; i--){ 
if(comp(arr[i], arr[i * 2])){exch(arr, i, i*2);} 
if(comp(arr[i], arr[i * 2 + 1])) {exch(arr, i, i*2 + 1);} 
} 
return arr[0];   
  
} 
   
function exch(arr,i,j){ 
var t = arr[i]; 
arr[i] = arr[j]; 
arr[j] = t; 
}

2. 调用K次堆实现,时间复杂度为 O(K * LogN)

function topK(arr,n,comp){ 
if(!arr || arr.length == 0 || n <=0 || n > arr.length){ 
return -1; 
} 
  
  
var ret = new Array(); 
for(var i = 0;i < n; i++){ 
var max = top(arr,comp); 
ret.push(max); 
arr.splice(0,1); 
} 
return ret; 
}

3.测试

var ret = topK(new Array(16,22,91,0,51,44,23),3,function (a,b){return a < b;}); 
console.log(ret);

以上就是为大家分享的使用堆实现Top K算法,何为Top K算法,希望对大家的学习有所帮助。

Javascript 相关文章推荐
【消息提示组件】,兼容IE6/7&amp;&amp;FF2
Sep 04 Javascript
关于viewport,Ext.panel和Ext.form.panel的关系
May 07 Javascript
Get中文乱码IE浏览器Get中文乱码解决方案
Dec 26 Javascript
用js格式化金额可设置保留的小数位数
May 09 Javascript
一段非常简单的js判断浏览器的内核
Aug 17 Javascript
js实现遮罩层弹出框的方法
Jan 15 Javascript
JS实现控制表格行文本对齐的方法
Mar 30 Javascript
完美实现仿QQ空间评论回复特效
May 06 Javascript
Ajax实现不刷新取最新商品
Mar 01 Javascript
使用vue-resource进行数据交互的实例
Sep 02 Javascript
ajaxfileupload.js实现上传文件功能
Apr 19 Javascript
Vue.js的模板语法详解
Feb 16 Javascript
原生js和jQuery实现淡入淡出轮播效果
Dec 25 #Javascript
jQuery实现模仿微博下拉滚动条加载数据效果
Dec 25 #Javascript
尝试动手制作javascript放大镜效果
Dec 25 #Javascript
js操作cookie保存浏览记录的方法
Dec 25 #Javascript
js实现跨域的多种方法
Dec 25 #Javascript
jquery.cookie.js用法实例详解
Dec 25 #Javascript
理解javascript中try...catch...finally
Dec 25 #Javascript
You might like
咖啡知识大全
2021/03/03 新手入门
组合算法的PHP解答方法
2012/02/04 PHP
laravel5.6 框架操作数据 Eloquent ORM用法示例
2020/01/26 PHP
Javascript和Java获取各种form表单信息的简单实例
2014/02/14 Javascript
jQuery中[attribute!=value]选择器用法实例
2014/12/31 Javascript
简述Jquery与DOM对象
2015/07/10 Javascript
jq实现左侧显示图片右侧文字滑动切换效果
2015/08/04 Javascript
javascript引用类型之时间Date和数组Array
2015/08/27 Javascript
AngularJS 遇到的小坑与技巧小结
2016/06/07 Javascript
Jquery ajax请求导出Excel表格的实现代码
2016/06/08 Javascript
实例解析jQuery中如何取消后续执行内容
2016/12/01 Javascript
JavaScript中双符号的运算详解
2017/03/12 Javascript
JS实现的点击按钮图片上下滚动效果示例
2019/01/28 Javascript
vue使用Font Awesome的方法步骤
2019/02/26 Javascript
JS实现选项卡效果的代码实例
2019/05/20 Javascript
vue+django实现一对一聊天功能的实例代码
2019/07/17 Javascript
Javascript实现秒表计时游戏
2020/05/27 Javascript
[47:12]TFT vs Secret Supermajor小组赛C组 BO3 第三场 6.3
2018/06/04 DOTA
[00:23]魔方之谜解锁款式
2018/12/20 DOTA
web.py获取上传文件名的正确方法
2014/08/26 Python
Python爬豆瓣电影实例
2018/02/23 Python
Python3.4解释器用法简单示例
2019/03/22 Python
python解析xml简单示例
2019/06/21 Python
PyQt5多线程刷新界面防假死示例
2019/12/13 Python
给 TensorFlow 变量进行赋值的方式
2020/02/10 Python
解决Keras中CNN输入维度报错问题
2020/06/29 Python
Melijoe美国官网:法国奢侈童装购物网站
2017/04/19 全球购物
在Ajax应用中信息是如何在浏览器和服务器之间传递的
2016/05/31 面试题
技术总监个人的自我评价范文
2013/12/18 职场文书
小学生演讲稿大全
2014/04/25 职场文书
实践单位评语
2014/04/26 职场文书
2014年中秋寄语
2014/08/11 职场文书
暑期学习心得体会
2014/09/02 职场文书
八年级数学教学反思
2016/02/17 职场文书
MySQL数据库中varchar类型的数字比较大小的方法
2021/11/17 MySQL
Go获取两个时区的时间差
2022/04/20 Golang