数据结构与算法之美学习笔记:29 | 堆的应用:如何快速获取到Top 10最热门的搜索关键词?

目录

  • 前言
  • 堆的应用一:优先级队列
  • 堆的应用二:利用堆求 Top K
  • 堆的应用三:利用堆求中位数
  • 解答开篇
  • 内容小结

前言

在这里插入图片描述
本节课程思维导图:
在这里插入图片描述
搜索引擎的热门搜索排行榜功能你用过吗?搜索引擎每天会接收大量的用户搜索请求,它会把这些用户输入的搜索关键词记录下来,然后再离线地统计分析,得到最热门的 Top 10 搜索关键词。
那请你思考下,假设现在我们有一个包含 10 亿个搜索关键词的日志文件,如何能快速获取到热门榜 Top 10 的搜索关键词呢?
这个问题就可以用堆来解决,今天我们就来讲一讲,堆这种数据结构几个非常重要的应用:优先级队列、求 Top K 和求中位数。

堆的应用一:优先级队列

优先级队列,顾名思义,它首先应该是一个队列。在优先级队列中,数据的出队顺序不是先进先出,而是按照优先级来,优先级最高的,最先出队。

如何实现一个优先级队列呢?一个堆就可以看作一个优先级队列。很多时候,它们只是概念上的区分而已。往优先级队列中插入一个元素,就相当于往堆中插入一个元素;从优先级队列中取出优先级最高的元素,就相当于取出堆顶元素。

优先级队列的应用场景非常多。比如,赫夫曼编码、图的最短路径、最小生成树算法等等。不仅如此,很多语言中,都提供了优先级队列的实现,比如,Java 的 PriorityQueue,C++ 的 priority_queue 等。
现在,我举两个具体的例子,让你感受一下优先级队列具体是怎么用的。

  1. 合并有序小文件

假设我们有 100 个小文件,每个文件的大小是 100MB,每个文件中存储的都是有序的字符串。我们希望将这些 100 个小文件合并成一个有序的大文件。这里就会用到优先级队列。
这里就可以用到优先级队列,也可以说是堆。我们将从小文件中取出来的字符串放入到小顶堆中,那堆顶的元素,也就是优先级队列队首的元素,就是最小的字符串。我们将这个字符串放入到大文件中,并将其从堆中删除。然后再从小文件中取出下一个字符串,放入到堆中。循环这个过程,就可以将 100 个小文件中的数据依次放入到大文件中。删除堆顶数据和往堆中插入数据的时间复杂度都是 O(logn),n 表示堆中的数据个数,这里就是 100。

  1. 高性能定时器
    假设我们有一个定时器,定时器中维护了很多定时任务,每个任务都设定了一个要触发执行的时间点。定时器每过一个很小的单位时间(比如 1 秒),就扫描一遍任务,看是否有任务到达设定的执行时间。如果到达了,就拿出来执行。

在这里插入图片描述
针对这些问题,我们就可以用优先级队列来解决。我们按照任务设定的执行时间,将这些任务存储在优先级队列中,队列首部(也就是小顶堆的堆顶)存储的是最先执行的任务。它拿队首任务的执行时间点,与当前时间点相减,得到一个时间间隔 T。这个时间间隔 T 就是,从当前时间开始,需要等待多久,才会有第一个任务需要被执行。这样,定时器就可以设定在 T 秒之后,再来执行任务。当 T 秒时间过去之后,定时器取优先级队列中队首的任务执行。然后再计算新的队首任务的执行时间点与当前时间点的差值,把这个值作为定时器执行下一个任务需要等待的时间。

堆的应用二:利用堆求 Top K

我们现在来看,堆的另外一个非常重要的应用场景,那就是“求 Top K 问题”。
我把这种求 Top K 的问题抽象成两类。一类是针对静态数据集合,也就是说数据集合事先确定,不会再变。另一类是针对动态数据集合,也就是说数据集合事先并不确定,有数据动态地加入到集合中。

针对静态数据,如何在一个包含 n 个数据的数组中,查找前 K 大数据呢?我们可以维护一个大小为 K 的小顶堆,顺序遍历数组,从数组中取出数据与堆顶元素比较。如果比堆顶元素大,我们就把堆顶元素删除,并且将这个元素插入到堆中;如果比堆顶元素小,则不做处理,继续遍历数组。这样等数组中的数据都遍历完之后,堆中的数据就是前 K 大数据了。

针对动态数据求得 Top K 就是实时 Top K。一个数据集合中有两个操作,一个是添加数据,另一个询问当前的前 K 大数据。
实际上,我们可以一直都维护一个 K 大小的小顶堆,当有数据被添加到集合中时,我们就拿它与堆顶的元素对比。如果比堆顶元素大,我们就把堆顶元素删除,并且将这个元素插入到堆中;如果比堆顶元素小,则不做处理。这样,无论任何时候需要查询当前的前 K 大数据,我们都可以立刻返回给他。

堆的应用三:利用堆求中位数

现在我们来讲下,如何求动态数据集合中的中位数。
中位数,顾名思义,就是处在中间位置的那个数。如果数据的个数是奇数,把数据从小到大排列,那第 n/2 +1 个数据就是中位数;如果数据的个数是偶数的话,那处于中间位置的数据有两个,第 n/2​ 个和第 n/2​+1 个数据,这个时候,我们可以随意取一个作为中位数,比如取两个数中靠前的那个,就是第 n/2 个数据。
在这里插入图片描述
对于一组静态数据,中位数是固定的,我们可以先排序,第 n/2​ 个数据就是中位数。每次询问中位数的时候,我们直接返回这个固定的值就好了。如果我们面对的是动态数据集合,借助堆这种数据结构,我们不用排序,就可以非常高效地实现求中位数操作。我们来看看,它是如何做到的?
我们需要维护两个堆,一个大顶堆,一个小顶堆。大顶堆中存储前半部分数据,小顶堆中存储后半部分数据,且小顶堆中的数据都大于大顶堆中的数据。
也就是说,如果有 n 个数据,n 是偶数,我们从小到大排序,那前 n/2 个数据存储在大顶堆中,后 2n​ 个数据存储在小顶堆中。这样,大顶堆中的堆顶元素就是我们要找的中位数。如果 n 是奇数,情况是类似的,大顶堆就存储n/2+1 个数据,小顶堆中就存储 n/2 个数据。
在这里插入图片描述
数据是动态变化的,当新添加一个数据的时候,我们如何调整两个堆,让大顶堆中的堆顶元素继续是中位数呢?如果新加入的数据小于等于大顶堆的堆顶元素,我们就将这个新数据插入到大顶堆;否则,我们就将这个新数据插入到小顶堆。
这个时候就有可能出现,两个堆中的数据个数不符合前面约定的情况:如果 n 是偶数,两个堆中的数据个数都是 n/2​;如果 n 是奇数,大顶堆有n/2​+1 个数据,小顶堆有 n/2​ 个数据。这个时候,我们可以从一个堆中不停地将堆顶元素移动到另一个堆,通过这样的调整,来让两个堆中的数据满足上面的约定。
在这里插入图片描述
于是,我们就可以利用两个堆,一个大顶堆、一个小顶堆,实现在动态数据集合中求中位数的操作。插入数据因为需要涉及堆化,所以时间复杂度变成了 O(logn),但是求中位数我们只需要返回大顶堆的堆顶元素就可以了,所以时间复杂度就是 O(1)。
实际上,利用两个堆不仅可以快速求出中位数,还可以快速求其他百分位的数据,原理是类似的。“如何快速求接口的 99% 响应时间?”我们现在就来看下,利用两个堆如何来实现。
,我先解释一下,什么是“99% 响应时间”?99 百分位数的概念可以类比中位数,如果将一组数据从小到大排列,这个 99 百分位数就是大于前面 99% 数据的那个数据。
在这里插入图片描述
我们维护两个堆,一个大顶堆,一个小顶堆。假设当前总数据的个数是 n,大顶堆中保存 n99% 个数据,小顶堆中保存 n1% 个数据。大顶堆堆顶的数据就是我们要找的 99% 响应时间。
每次插入一个数据的时候,我们要判断这个数据跟大顶堆和小顶堆堆顶数据的大小关系,然后决定插入到哪个堆中。如果这个新插入的数据比大顶堆的堆顶数据小,那就插入大顶堆;反之则插入小顶堆。
但是,为了保持大顶堆中的数据占 99%,小顶堆中的数据占 1%,在每次新插入数据之后,我们都要重新计算,这个时候大顶堆和小顶堆中的数据个数,是否还符合 99:1 这个比例。如果不符合,我们就将一个堆中的数据移动到另一个堆,直到满足这个比例。

解答开篇

假设现在我们有一个包含 10 亿个搜索关键词的日志文件,如何快速获取到 Top 10 最热门的搜索关键词呢?如果我们将处理的场景限定为单机,可以使用的内存为 1GB。那这个问题该如何解决呢?

我们创建 10 个空文件 00,01,02,……,09。我们遍历这 10 亿个关键词,并且通过某个哈希算法对其求哈希值,然后哈希值同 10 取模,得到的结果就是这个搜索关键词应该被分到的文件编号。对这 10 亿个关键词分片之后,每个文件都只有 1 亿的关键词,去除掉重复的,可能就只有 1000 万个,每个关键词平均 50 个字节,所以总的大小就是 500MB。1GB 的内存完全可以放得下。
我们针对每个包含 1 亿条搜索关键词的文件,利用散列表和堆,分别求出 Top 10。
具体我们选用散列表。我们就分别扫描这 10个文件。当扫描到某个关键词时,我们去散列表中查询。如果存在,我们就将对应的次数加一;如果不存在,我们就将它插入到散列表,并记录次数为 1。这样我们就能得到每个文件中关键词的个数;我们再根据前面讲的用堆求 Top K 的方法,建立一个大小为 10 的小顶堆,遍历散列表,依次取出每个搜索关键词及对应出现的次数,然后与堆顶的搜索关键词对比。如果出现次数比堆顶搜索关键词的次数多,那就删除堆顶的关键词,将这个出现次数更多的关键词加入到堆中。以此类推,当遍历完整个散列表中的搜索关键词之后,我们就得到100 个使用次数最多的关键词,然后取这 100 个关键词中,出现次数最多的 10 个关键词,这就是这 10 亿数据中的 Top 10 最频繁的搜索关键词了。

内容小结

我们今天主要讲了堆的几个重要的应用,它们分别是:优先级队列、求 Top K 问题和求中位数问题。
优先级队列是一种特殊的队列,优先级高的数据先出队,而不再像普通的队列那样,先进先出。实际上,堆就可以看作优先级队列,只是称谓不一样罢了。
求 Top K 问题又可以分为针对静态数据和针对动态数据,只需要利用一个堆,就可以做到非常高效率地查询 Top K 的数据。
求中位数实际上还有很多变形,比如求 99 百分位数据、90 百分位数据等,处理的思路都是一样的,即利用两个堆,一个大顶堆,一个小顶堆,随着数据的动态添加,动态调整两个堆中的数据,最后大顶堆的堆顶元素就是要求的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/208072.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Shell循环:for(三)

示例:使用for实现批量主机root密码的修改 一、前提 已完成密钥登录配置(ssh-keygen)定义主机地址列表并了解远程修改密码的方法 [rootlocalhost ~]# ssh-keygen #设置免密登录[rootlocalhost ~]# ssh-copy-id 192.168.151.151 二、演示…

Linux进程详解

Linux进程详解 1、进程概述1.1并行和并发1.2 PCB1.3 进程状态1.4 进程命令 2、进程创建2.1 函数2.2 fork()解析 3、父子进程3.1 进程执行位置3.2 循环创建子进程3.3 终端显示问题3.4 进程数数 4、execl和execlp4.1 execl()4.2 execlp()4.3 函数的使用 5、进程控制5.1 结束进程5…

Oracle忘记所有密码怎么办

最近遇到一个Oracle的问题,密码要过期了,但是除了用户密码,其他密码都不知道了,修改不了密码怎么办呢? 试了各种方法,最终下面的方式生效了: 首先,使用orapwd生成新的密码文件&…

selenium 工具 的基本使用

公司每天要做工作汇报,汇报使用的网页版, 所以又想起 selenium 这个老朋友了。 再次上手,发现很多接口都变了, 怎么说呢, 应该是易用性更强了, 不过还是得重新看看, 我这里是python3。 pip安装…

有文件实体的后门无文件实体的后门rootkit后门

有文件实体后门和无文件实体后门&RootKit后门 什么是有文件的实体后门: 在传统的webshell当中,后门代码都是可以精确定位到某一个文件上去的,你可以rm删除它,可以鼠标右键操作它,它是有一个文件实体对象存在的。…

熬夜会秃头——beta冲刺Day4

这个作业属于哪个课程2301-计算机学院-软件工程社区-CSDN社区云这个作业要求在哪里团队作业—beta冲刺事后诸葛亮-CSDN社区这个作业的目标记录beta冲刺Day4团队名称熬夜会秃头团队置顶集合随笔链接熬夜会秃头——Beta冲刺置顶随笔-CSDN社区 一、团队成员会议总结 1、成员工作进…

(详细教程)笔记本电脑安装Ubuntu系统

1.前言 老的小米笔记本淘汰了,装一下linux系统玩一下。 使用工具如下:一台小米笔记本pro15.6一个惠普32G U盘一个台式机用于下载镜像等资源 2.下载Ubuntu桌面版 cn.ubuntu.com/download/de… 这里我下载的是 22.04.3 LTS 3.下载烧录工具&#xff0c…

Lattice-Based Blind Signatures: Short, Efficient, and Round-Optimal

目录 摘要引言 Lattice-Based Blind Signatures: Short, Efficient, and Round-Optimal CCS 2023 摘要 我们提出了一种基于随机预言机启发式和标准格问题(环/模块SIS/LWE和NTRU)的2轮盲签名协议,签名大小为22KB。该协议是全面优化的&#xf…

如何做接口测试?接口测试工具有哪些?

回想入职测试已经10年时间了,初入职场的我对于接口测试茫然不知。后来因为业务需要,开始慢慢接触接口测试。从最开始使用工具进行接口测试到编写代码实现接口自动化,到最后的测试平台开发。回想这一路走来感触颇深,因此为了避免打…

分享82个节日PPT,总有一款适合您

分享82个节日PPT,总有一款适合您 82个节日PPT下载链接:https://pan.baidu.com/s/1boDTl3PiHFXLJ890CoUfJA?pwd8888 提取码:8888 Python采集代码下载链接:采集代码.zip - 蓝奏云 学习知识费力气,收集整理更不易。…

MathType 7.5.2中文版软件使用期到了怎么办?

MathType 7.5.2中文版作为一款专业的公式编辑器,MathType受到很多人的青睐,它可以将编辑好的公式保存成多种图片格式或透明图片模式,可以很方便的添加或移除符号、表达式等模板(只需要简单地用鼠标拖进拖出即可),也可以…

39.从0到上线三天搭建个人网站(第三天)

点赞收藏加关注,你也能住大别墅! 一、第三天主要工作 1.完成detail页面的开发 2.将所有数据以及部分静态资源存在uniCloud,为以后做管理后台做准备 3.创建云对象getData,在beforecreate()中获取数据 4.…

详解原生Spring框架下的类切入点表达式与切入点函数

😉😉 学习交流群: ✅✅1:这是孙哥suns给大家的福利! ✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 🥭🥭3:QQ群:583783…

126. 单词接龙 II

126. 单词接龙 II 需要注意的是,由于要找最短路径,连接 dot 与 lot 之间的边就不可以被记录下来,同理连接 dog 与 log 之间的边也不可以被记录。这是因为经过它们的边一定不会是最短路径。因此在广度优先遍历的时候,需要记录的图…

分享88个节日PPT,总有一款适合您

分享88个节日PPT,总有一款适合您 88个节日PPT下载链接:https://pan.baidu.com/s/1mfLrdlB9Y1jqz2vkVIwBNA?pwd6666 提取码:6666 Python采集代码下载链接:采集代码.zip - 蓝奏云 学习知识费力气,收集整理更不易…

Linux部分基础指令讲解

目录 1.echo指令 2.more指令 3.less指令(重要) 4.head指令 5.tail指令 6.管道| 7.时间相关的指令 8.cal指令 9.find指令 10.grep指令 1.echo指令 我们先看效果 如图所示我们可以看到显示器显示出了hellow world和hellow这两句话,我们的echo的…

超分辨率重建

意义 客观世界的场景含有丰富多彩的信息,但是由于受到硬件设备的成像条件和成像方式的限制,难以获得原始场景中的所有信息。而且,硬件设备分辨率的限制会不可避免地使图像丢失某些高频细节信息。在当今信息迅猛发展的时代,在卫星…

【EI会议征稿】第四届生物信息学与智能计算国际学术研讨会(BIC 2024)

第四届生物信息学与智能计算国际学术研讨会(BIC 2024) 2024 4th International Conference on Bioinformatics and Intelligent Computing 2024年第四届生物信息学与智能计算国际学术研讨会 (BIC 2024)将定于2024年1月26-28日在…

Golang数据类型(数字型)

Go数据类型(数字型) Go中数字型数据类型大致分为整数(integer)、浮点数(floating point )和复数(Complex)三种 整数重要概念 整数在Go和Python中有较大区别,主要体现在…

C++的explicit和隐式转换

隐式转换是指在某些情况下,编译器会自动进行类型转换,将一种类型的值转换为另一种类型,以满足表达式的要求。这种转换是隐式进行的,不需要显式地调用转换函数或构造函数。 int a 5; double b a; // int 到 double 的隐式转换上…