使用堆实现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 相关文章推荐
Document:getElementsByName()使用方法及示例
Oct 28 Javascript
获取鼠标在div中的相对位置的实现代码
Dec 30 Javascript
JavaScript中的document.referrer在各种浏览器测试结果
Jul 18 Javascript
jQuery使用addClass()方法给元素添加多个class样式
Mar 26 Javascript
jQuery实现仿美橙互联两级导航菜单效果完整实例
Sep 17 Javascript
谈谈JavaScript自定义回调函数
Oct 18 Javascript
Bootstrap轮播插件使用代码
Oct 11 Javascript
从零学习node.js之express入门(六)
Feb 25 Javascript
收藏AngularJS中最重要的核心功能
Jul 09 Javascript
JavaScript Image对象实现原理实例解析
Aug 26 Javascript
Canvas跟随鼠标炫彩小球的实现
Apr 11 Javascript
vue 实现上传组件
May 31 Vue.js
原生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正则表达匹配中文问题分析小结
2012/03/25 PHP
PHP长连接实现与使用方法详解
2018/02/11 PHP
PHP 实现文件压缩解压操作的方法
2019/06/14 PHP
Sample script that deletes a SQL Server database
2007/06/16 Javascript
js中更短的 Array 类型转换
2011/10/30 Javascript
Eclipse配置Javascript开发环境图文教程
2015/01/29 Javascript
js中for in语句的用法讲解
2015/04/24 Javascript
javascript实现支持移动设备画廊
2015/08/24 Javascript
jQuery插件Validate实现自定义校验结果样式
2016/01/18 Javascript
JS匿名函数实例分析
2016/11/26 Javascript
JS基于正则表达式的替换操作(replace)用法示例
2017/04/28 Javascript
学习使用Bootstrap输入框、导航、分页等常用组件
2017/05/11 Javascript
webpack分离css单独打包的方法
2018/06/12 Javascript
JS判断字符串是否为整数的方法--简单的正则判断
2018/07/23 Javascript
Vue项目部署的实现(阿里云+Nginx代理+PM2)
2019/03/26 Javascript
通过实例解析js简易模块加载器
2019/06/17 Javascript
小程序如何在不同设备上自适应生成海报的实现方法
2019/08/20 Javascript
[00:10]神之谴戒
2019/03/06 DOTA
[45:34]完美世界DOTA2联赛PWL S3 Rebirth vs CPG 第一场 12.18
2020/12/19 DOTA
python logging类库使用例子
2014/11/22 Python
Python实现读取邮箱中的邮件功能示例【含文本及附件】
2017/08/05 Python
html5视频播放_动力节点Java学院整理
2017/07/13 HTML / CSS
Weekendesk意大利:探索多种引人入胜的周末主题
2016/10/14 全球购物
全球速卖通:AliExpress(国际版淘宝)
2017/09/20 全球购物
英国翻新电子产品购物网站:Tech Trade
2017/12/25 全球购物
swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上?
2013/03/30 面试题
计算机通信工程专业毕业生推荐信
2013/12/24 职场文书
教师通用专业自荐书范文
2014/02/11 职场文书
目标责任书范文
2014/04/14 职场文书
竞选纪律委员演讲稿
2014/09/13 职场文书
解除同居协议书
2015/01/29 职场文书
宇宙与人观后感
2015/06/05 职场文书
欠条样本
2015/07/03 职场文书
教你如何用python开发一款数字推盘小游戏
2021/04/14 Python
用Python实现屏幕截图详解
2022/01/22 Python
实战Python爬虫爬取酷我音乐
2022/04/11 Python