使用堆实现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 相关文章推荐
JavaScript 对象成员的可见性说明
Oct 16 Javascript
js动态加载以及确定加载完成的代码
Jul 31 Javascript
dojo学习第一天 Tab选项卡 实现
Aug 28 Javascript
event.X和event.clientX的区别分析
Oct 06 Javascript
js 输出内容到新窗口具体实现代码
May 31 Javascript
JavaScript中的DSL元编程介绍
Mar 15 Javascript
基于javascript实现简单的抽奖系统
Apr 15 Javascript
JS 实现倒计时数字时钟效果【附实例代码】
Mar 30 Javascript
浅谈js的html元素的父节点,子节点
Aug 06 Javascript
新手vue构建单页面应用实例代码
Sep 18 Javascript
vue data引入本地图片的两种方式小结
Nov 13 Javascript
JS的深浅复制详细
Oct 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
自己前几天写的无限分类类
2007/02/14 PHP
php中使用ExcelFileParser处理excel获得数据(可作批量导入到数据库使用)
2010/08/21 PHP
Laravel日志用法详解
2016/10/09 PHP
Javascript中的Split使用方法与技巧
2007/03/09 Javascript
基于JQuery制作的产品广告效果
2010/12/08 Javascript
jQuery点击tr实现checkbox选中的方法
2013/03/19 Javascript
Express作者TJ告别Node.js奔向Go
2014/07/14 Javascript
JavaScript中constructor()方法的使用简介
2015/06/05 Javascript
JS组件Bootstrap Select2使用方法解析
2016/05/30 Javascript
利用python分析access日志的方法
2016/10/26 Javascript
Bootstrap 3 进度条的实现
2017/02/22 Javascript
详解vue-router 路由元信息
2017/09/13 Javascript
jQuery基于cookie实现换肤功能实例
2017/10/14 jQuery
vue刷新页面时去闪烁提升用户体验效果的实现方法
2018/12/10 Javascript
在微信小程序中使用图表的方法示例
2019/04/25 Javascript
vue中引入mxGraph的步骤详解
2019/05/17 Javascript
手把手教你 CKEDITOR 4 实现Dialog 内嵌 IFrame操作详解
2019/06/18 Javascript
js实现弹窗猜数字游戏
2020/11/26 Javascript
[00:17]DOTA2荣耀之路5:It’s a disastah!
2018/05/28 DOTA
python使用ctypes模块调用windowsapi获取系统版本示例
2014/04/17 Python
仅用50行代码实现一个Python编写的计算器的教程
2015/04/17 Python
使用Python监控文件内容变化代码实例
2018/06/04 Python
Python实现的从右到左字符串替换方法示例
2018/07/06 Python
python批量复制图片到另一个文件夹
2018/09/17 Python
Python 支付整合开发包的实现
2019/01/23 Python
python模拟点击网页按钮实现方法
2020/02/25 Python
python标准库OS模块详解
2020/03/10 Python
教你一分钟在win10终端成功安装Pytorch的方法步骤
2021/01/28 Python
html5服务器推送_动力节点Java学院整理
2017/07/12 HTML / CSS
Move Free官方海外旗舰店:美国骨关节健康专业品牌
2017/12/06 全球购物
extern在函数声明中是什么意思
2014/01/19 面试题
董事长秘书职责
2014/01/31 职场文书
医院节能减排方案
2014/06/13 职场文书
教师节活动总结
2014/08/29 职场文书
教你用python控制安卓手机
2021/05/13 Python
pandas数值排序的实现实例
2021/07/25 Python