使用堆实现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 相关文章推荐
一些技巧性实用js代码小结
Oct 14 Javascript
基于jquery的$.ajax async使用
Oct 19 Javascript
Javascript创建自定义对象 创建Object实例添加属性和方法
Jun 04 Javascript
Web表单提交之disabled问题js解决方法
Jan 13 Javascript
JS模态窗口返回值兼容问题的完美解决方法
May 28 Javascript
字太多用...代替的方法(两种)
Mar 15 Javascript
JS中正则表达式要注意lastIndex属性
Aug 08 Javascript
vue单页缓存存在的问题及解决方案(小结)
Sep 25 Javascript
vue组件之间数据传递的方法实例分析
Feb 12 Javascript
9102年webpack4搭建vue项目的方法步骤
Feb 20 Javascript
浅析Vue中拆分视图层代码的5点建议
Aug 15 Javascript
Vue 数据绑定的原理分析
Nov 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
PHP函数超时处理方法
2016/02/14 PHP
thinkPHP实现将excel导入到数据库中的方法
2016/04/22 PHP
PHP使用strrev翻转中文乱码问题的解决方法
2017/01/13 PHP
php如何修改SESSION的生存存储时间的实例代码
2017/07/05 PHP
php微信公众号开发之音乐信息
2018/10/20 PHP
tp5框架使用composer实现日志记录功能示例
2019/01/10 PHP
JavaScript的面向对象(二)
2006/11/09 Javascript
JQuery 前台切换网站的样式实现
2009/06/22 Javascript
ASP.NET jQuery 实例6 (实现CheckBoxList成员全选或全取消)
2012/01/13 Javascript
关于JS中的闭包浅谈
2013/08/23 Javascript
解决用jquery load加载页面到div时,不执行页面js的问题
2014/02/22 Javascript
javascript中return,return true,return false三者的用法及区别
2015/11/17 Javascript
AngularJS单选框及多选框实现双向动态绑定
2016/01/13 Javascript
高性能JavaScript循环语句和条件语句
2016/01/20 Javascript
深入理解逻辑表达式的用法 与或非的用法
2016/06/06 Javascript
JS原型链 详解及示例代码
2016/09/06 Javascript
vue实现todolist单页面应用
2017/04/11 Javascript
jquery处理checkbox(复选框)是否被选中实例代码
2017/06/12 jQuery
关闭Vue计算属性自带的缓存功能方法
2018/03/02 Javascript
JS实现的类似微信聊天效果示例
2019/01/29 Javascript
Vue.js页面中有多个input搜索框如何实现防抖操作
2019/11/04 Javascript
小程序登录之支付宝授权的实现示例
2019/12/13 Javascript
JS数据类型STRING使用实例解析
2019/12/18 Javascript
微信小程序自定义yPicker组件实现省市区三级联动功能
2020/10/29 Javascript
Python中的多重装饰器
2015/04/11 Python
Python实例一个类背后发生了什么
2016/02/09 Python
浅谈Python的list中的选取范围
2018/11/12 Python
python set内置函数的具体使用
2019/07/02 Python
python 递归调用返回None的问题及解决方法
2020/03/16 Python
浅析几个CSS3常用功能的写法
2014/06/05 HTML / CSS
国际商务系学生个人的自我评价
2013/11/26 职场文书
会计试用期自我评价
2014/09/19 职场文书
2016年毕业实习心得体会范文
2015/10/09 职场文书
2019求职信:应届生求职信范文
2019/04/24 职场文书
详解使用 CSS prefers-* 规范提升网站的可访问性与健壮性
2021/05/25 HTML / CSS
Android Rxjava3 使用场景详解
2022/04/07 Java/Android