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

先来聊一聊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算法,希望对大家的学习有所帮助。

(0)

相关推荐

  • Javascript堆排序算法详解

    堆排序分为两个过程: 1.建堆. 堆实质上是完全二叉树,必须满足:树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字. 堆分为:大根堆和小根堆,升序排序采用大根堆,降序排序采用小根堆. 如果是大根堆,则通过调整函数将值最大的节点调整至堆根. 2.将堆根保存于尾部,并对剩余序列调用调整函数,调整完成后,再将最大跟保存于尾部-1(-1,-2,...,-i),再对剩余序列进行调整,反复进行该过程,直至排序完成. 复制代码 代码如下: //调整函数 function head

  • 详解堆的javascript实现方法

    堆的定义 最大(最小)堆是一棵每一个节点的键值都不小于(大于)其孩子(如果存在)的键值的树.大顶堆是一棵完全二叉树,同时也是一棵最大树.小顶堆是一棵完全完全二叉树,同时也是一棵最小树. 另外,记住这两个概念,对写代码太重要了: 1.父节点和子节点的关系:看定义 2.完全二叉树:参考[2] 基本操作 1.Build(构建堆) 2.Insert(插入) 3.Delete(删除:最小或者最大的那个) 代码实现 首先,写代码前有两个非常重要的点: 1.用一个数组就可以作为堆的存储结构,非常简单而且易操作

  • js自动生成的元素与页面原有元素发生堆叠的解决方法

     商品属性和商品规格是js动态生成的元素,商品扩展信息的两个文本框是原有的元素,他们发生堆叠,我以为是我生成的元素所在div大小不固定导致的,因为商品规格的下面复选框是第二次ajax生成的,我怀疑第二次ajax是不是不能将页面原有元素向下推到合适的位置. 搞了几个小时,尝试固定元素所在容器div的的大小,但是不好固定啊,元素的个数是不定的,尝试改变属性和规格的生成顺序,属性部分堆到规格部分上去了,规格部分的元素怎么不独立占位置呢,后来才想到会不会是浮动了,去除浮动,给原有元素(商品扩展信息部分)

  • JS实现队列与堆栈的方法

    本文实例讲述了JS实现队列与堆栈的方法.分享给大家供大家参考,具体如下: 在面向对象的程序设计里,一般都提供了实现队列(queue)和堆栈(stack)的方法,而对于JS来说,我们可以实现数组的相关操作,来实现队列和堆栈的功能,看下面的相关介绍. 一.看一下它们的性质,这种性质决定了它们的使用场合 队列:是一种支持先进先出(FIFO)的集合,即先被插入的数据,先被取出! 堆栈:是一种支持后进先出(LIFO)的集合,即后被插入的数据,先被取出! 二.看一下实现的代码(JS代码) var a=new

  • JavaScript调用堆栈及setTimeout使用方法深入剖析

    Javascript中会经常用到setTimeout来推迟一个函数的执行,如: 复制代码 代码如下: setTimeout(function(){alert("Hello World");},1000); 会在执行到这句话后延迟1秒钟来弹出alert窗口.那么再看这一段: 复制代码 代码如下: function a(){ setTimeout(function() {alert(1)}, 0); alert(2); } a(); 注意这段代码中的setTimeout延迟设为了0,就是延

  • JavaScript数组实现数据结构中的队列与堆栈

    一.队列和堆栈的简单介绍 1.1.队列的基本概念 队列:是一种支持先进先出(FIFO)的集合,即先被插入的数据,先被取出! 如下图所示: 1.2.堆栈的基本概念 堆栈:是一种支持后进先出(LIFO)的集合,即后被插入的数据,先被取出! 如下图所示: 二. 在JavaScript中实现队列和堆栈 在JavaScript中实现队列和数组主要是通过数组,js数组中提供了以下几个方法可以让我们很方便实现队列和堆栈: •shift:从数组中把第一个元素删除,并返回这个元素的值. •unshift: 在数组

  • JavaScript把数组作为堆栈使用的方法

    本文实例讲述了JavaScript把数组作为堆栈使用的方法.分享给大家供大家参考.具体如下: JavaScript把数组作为堆栈使用的代码范例,支持堆栈常用的push和pop方法 <script type="text/javascript"> var numbers = ["one", "two", "three", "four"]; numbers.push("five")

  • java自带的工具Jstack截取进程中的堆栈信息

    在Java软件的使用过程中,有时会莫名的出现奇怪的问题.而这些问题常常无法使用日志信息定位,这时我们就需要通过查看进程内部线程的堆栈调用关系来分析问题出在哪里. 举个例子,当我们在做某个操作时,莫名的会弹出多个警告框,其中有些信息是正常的,有些则不是.对于这些错误的警告信息,我们该如何定位是哪个位置的代码出现了错误弹出的框呢? 我们就需要在弹框以后,去查看软件的各个线程,去查找究竟是哪个线程导致了该问题.可是有时因为环境.时间等问题,我们根本不能拿着IDE去调试, 只能通过工具软件拍下内存快照,

  • JavaScript实现显示函数调用堆栈的方法

    本文实例讲述了JavaScript实现显示函数调用堆栈的方法.分享给大家供大家参考,具体如下: 许多大型的JavaScript应用程序间的函数调用关系是非常复杂的,在开发或者调试过程中,经常需要跟踪某个函数是由哪些函数调用后才触发执行的,弄清楚这些函数的调用顺序对我们理解代码的数据流向是非常重要的. Firebug提供了console.trace()来显示函数堆栈,在需要调试的地方加上下面的一行代码就能显示该函数调用时的上下文关系.IE6就没有这么方便了,它没有提供显示函数堆栈的工具,当不可避免

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

    先来聊一聊Top K算法,具体内容如下 应用场景: 搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节.         假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个.一个查询串的重复度越高,说明查询它的用户越多,也就是越热门.),请你统计最热门的10个查询串,要求使用的内存不能超过1G. 必备知识: 什么是哈希表?         哈希表(Hash table,也叫散列表),是根据关键码值(K

  • Trojan.DL.VBS.Agent.cpb(k[1].js)脚本病毒的解决方法

    脚本病毒:Trojan.DL.VBS.Agent.cpb (文件名为k[1].js)老是在internet临时文件里出现,瑞星监控杀了又来,如此反复着!我试图清空临时文件,但一上网打开网页(不管是哪些网页),那个k[1].js又会被瑞星监控到.这是怎么回事呀?是误报吗? 该网页利用MS06-014漏洞,下载http://day.91tg.net/xp.dll到C:\WINDOWS\winhelp.dll,并直接写入注册表 Code: HKLM\SOFTWARE\Classes\CLSID\{6B

  • JAVA 中解密RSA算法JS加密实例详解

    JAVA 中解密RSA算法JS加密实例详解 有这样一个需求,前端登录的用户名密码,密码必需加密,但不可使用MD5,因为后台要检测密码的复杂度,那么在保证安全的前提下将密码传到后台呢,答案就是使用RSA非对称加密算法解决 . java代码 需要依赖 commons-codec 包 RSACoder.Java import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import java.security.

  • javascript获取图片的top N主色值方法详解

    题目要求 找出一个页面中出现次数最多的标签!!! 个人解法: var eles = document.getElementsByTagName('*'); var rs = []; for(var i=0; i<eles.length; i++) { var tag_name = eles[i].tagName.toLowerCase(); if(undefined != tag_name) { if(inJsonArray(rs, tag_name)) { addWeight(rs, tag_

  • Python中的优先队列(priority queue)和堆(heap)

    目录 队列和优先队列(Priority Queue) 堆(heap) 简介 初始化构建堆 堆的插入(节点上浮) 堆的删除(节点下浮) 堆的应用 队列和优先队列(Priority Queue) 队列是一种可以完成插入和删除的数据结构.普通队列是先进先出(FIFO), 即先插入的先被删除. 然而在某些时候我们需要按照任务的优先级顺序来决定出队列的顺序,这个时候就需要用到优先级队列了.优先队列是一种可以完成插入和删除最小元素的数据结构 python中有现成的优先队列类可以调用. 代码示例 from q

  • javascript中可能用得到的全部的排序算法

    导读 排序算法可以称得上是我的盲点, 曾几何时当我知道Chrome的Array.prototype.sort使用了快速排序时, 我的内心是奔溃的(啥是快排, 我只知道冒泡啊?!), 要知道学习一门技术最好的时间是三年前, 但愿我现在补习还来得及(捂脸). 因此本篇重拾了出镜概率比较高的十来种排序算法, 逐一分析其排序思想, 并批注注意事项. 欢迎对算法提出改进和讨论. 冒泡排序 冒泡排序需要两个嵌套的循环. 其中, 外层循环移动游标; 内层循环遍历游标及之后(或之前)的元素, 通过两两交换的方式

  • Java面试题冲刺第二十三天--算法(2)

    目录 面试题1:你说一下常用的排序算法都有哪些? 追问1:谈一谈你对快排的理解吧 追问2:说一下快排的算法原理 追问3:来吧!给我手敲一个快排 面试题2:来!再给我手撸一个Spring 追问1:哦,咳咳-说一下构成递归的前提条件有啥? 追问2:递归都有哪些优缺点? 追问3:给我手写一个简单的递归算法的实现吧 面试题3: 10亿个数中找出最大的100000个数(top K问题) 总结 面试题1:你说一下常用的排序算法都有哪些? 追问1:谈一谈你对快排的理解吧 快速排序,顾名思义就是一种以效率快为特

  • Python实现二叉堆

    优先队列的二叉堆实现 在前面的章节里我们学习了"先进先出"(FIFO)的数据结构:队列(Queue).队列有一种变体叫做"优先队列"(Priority Queue).优先队列的出队(Dequeue)操作和队列一样,都是从队首出队.但在优先队列的内部,元素的次序却是由"优先级"来决定:高优先级的元素排在队首,而低优先级的元素则排在后面.这样,优先队列的入队(Enqueue)操作就比较复杂,需要将元素根据优先级尽量排到队列前面.我们将会发现,对于下一

  • js/jq仿window文件夹移动/剪切/复制等操作代码

    window对文件夹的操作主要包括移动/剪切/复制,本篇文章主要用jQuery来实现,下面一起来了解一下把. 1.先看下效果吧! 2.在添加一个index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./jquery-1.

  • Javascript中的常见排序算法

    具体代码及比较如下所示: 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  <html xmlns="http://www.w3.org/1999/xhtml" lang="gb2312">

随机推荐