【数据结构】(9) 优先级队列(堆)
一、优先级队列
优先级队列不同于队列,队列是先进先出,优先级队列是优先级最高的先出。一般有两种操作:返回最高优先级对象,添加一个新对象。
二、堆
2.1、什么是堆
堆也是一种数据结构,是一棵完全二叉树,该完全二叉树中的所有子树的根都大于其孩子,即大根堆;如果都小于其孩子,就是小根堆。
2.2、堆的存储方式
由于完全二叉树按层序遍历,节点之间没有空隙,所以存储在顺序表中不会造成空间浪费;并且顺序存储方便访问二叉树中某节点 i 的双亲((i-1)/2)和孩子(左:2*i+1,右:2*i+2),以便调整堆。因此,堆采用顺序结构存储。
大根堆示例:

三、优先级队列(堆)的模拟实现
PriorityQueue 底层是用堆实现的。
3.1、将完全二叉树调整为堆(向下调整)
3.1.1、手动模拟过程
调整下面的完全二叉树为堆。

从最后一个父节点开始调整(parent=((usedSize-1)-1)/2):将较大的孩子 child 与父节点比较(没有右孩子不需要比较孩子大小),若 child 更大则于父节点交换。

调整倒数第 2 棵子树(parent--)。

调整倒数第 3 棵子树。

调整倒数第 4 棵子树。

如果p和c交换了,还要调整其子树(p=c),直到 p 和 c 没有交换为止,或者 c 超过二叉树大小 usedSize 为止。(向下调整)
重复上述操作,直到调整完 p=0 的树。

3.1.2、代码实现
向下调整过程,时间复杂度推导:如果某棵子树高为 k,则最多交换 k-1 次,高为 k 的完全二叉树最多有 N=(2^k)-1 个结点,因此 k-1 = (log(N+1)-1),时间复杂度为 O(logN)。
/*** 向下调整* 时间复杂度:O(logN)* @param parent 子树根节点的下标* @param size 子树的大小*/private void shiftDown(int parent, int size) {int maxChild = 2*parent+1; // 默认最大孩子为左孩子while(maxChild < size) { // 存在左孩子就继续调整if(maxChild+1 < size && arr[maxChild+1] > arr[maxChild]) { // 存在右孩子且右孩子大于左孩子maxChild++;}if (arr[maxChild] > arr[parent]) { // 最大孩子大于父节点,交换swap(parent, maxChild);parent = maxChild; // 继续调整子树maxChild = 2*parent+1;}else { // 没有交换,结束调整break;}}}
将完全二叉树调整为堆,时间复杂度推导:

T=1*(h-1)+2^1*(h-2)+……+2^(h-2)*1。
2T=2^1*(h-1)+2^2*(h-2)+……+2^(h-1)*1。
2T-T=T=2^1+2^2+……+2^(h-2)+2^(h-1)-h+1=2^h-1-h=2^log(N+1)-1-log(N+1)=N-log(N+1)
时间复杂度为 O(N)。
/*** 将一棵完全二叉树调整为大根堆* 时间复杂度:O(N)*/public void shift2Heap() {int initParent = ((usedSize - 1)-1)/2;for (int i = initParent; i >= 0; i--) {shiftDown(i, usedSize);}}
测试结果:


3.2、堆中插入一个新节点(向上调整)
另一种创建堆的方法是,每插入一个新节点,就调整二叉树为堆。
3.2.1、手动模拟过程
在结尾插入新结点80,并从最后一棵子树开始(child=usedSize-1),从下往上调整,直到 parent=0,或者没有交换为止。(向上调整)

child=parent。


3.2.2、代码实现
向上调整:
/*** 向上调整* 时间复杂度:O(logN)* @param child 添加的孩子节点的下标*/private void shiftUp(int child) {int parent = (child-1)/2; // 父节点下标while(parent >= 0 && arr[child] > arr[parent]) { // 父节点存在且孩子大于父节点,交换swap(child, parent);child = parent;parent = (child-1)/2; // 继续向上调整}}
插入一个新节点:
/*** 添加元素到堆中* 时间复杂度:O(NlogN)*/public void offer(int val) {if (isFull()) { // 数组已满,扩容arr = Arrays.copyOf(arr, arr.length * 2);}arr[usedSize] = val;usedSize++;shiftUp(usedSize-1); // 向上调整}
测试:


如果插入 N 个结点,时间复杂度推导:

T=2*1+2^2*2+……+2^(h-2)*(h-2)+2^(h-1)*(h-1)
2T=2^2+2^3*2+……+2^(h-1)*(h-2)+2^h*(h-1)
2T-T=T=2^h*(h-1)-2^2-2^3-……-2^(h-1)-2=2^h*(h-1)-2*(2^(h-1)-1)
=2^h*(h-1)-2^h+2=2+2^h*(h-2)+2=(N+1)*(log(N+1)-2)+2
时间复杂度为:O(NlogN)
3.3、删除堆顶元素
步骤:
- 堆顶与堆尾元素交换。
- 删除堆尾元素。
- 对堆从树根开始向下调整。
public int poll() {if (isEmpty()) {return -1;}int ret = arr[0];arr[0] = arr[usedSize-1];usedSize--;shiftDown(0, usedSize); // 向下调整return ret;}
四、PriorityQueue 的使用
4.1、注意事项
- PriorityQueue 是线程不安全的,PriorityBlockingQueue 是线程安全的。
- PriorityQueue 中放置的元素必须是可比较大小的,否则会抛出异常。
- 不能插入 null,否则会抛出异常。
而我们之前学习的 LinkList 是可以插入 null 的:

- 默认构建小根堆。
4.2、构造函数的使用
无参构造器:默认容量 11,默认比较器为空。
![]()

带初始容量参数的构造器:指定初始容量,默认比较器为空。

用一个集合创建优先级队列:传入某集合对象。

因为 String 不是 Number 及其子类,所以语法错误。

Integer 是 Number 的子类,成功调用。

指定初始容量、比较器的构造函数:

transient 的作用:让不想被序列化的属性不被序列化,保证信息的隐私,比如密码。序列化就是把对象的信息转成字符串放到磁盘里;反序列化就是把磁盘里的字符串转成对象信息。transient 就让修饰的属性信息的生命周期仅在调用者的内存中,而不会写进磁盘中持久化。

4.3、offer 插入一个元素
插入一个元素,offer 源码:

扩容,grow 源码:



向上调整,shiftUp 源码:

如果元素是基础类型的包装类,会使用自身重写的 compareTo:


如果构造函数参数指定了比较器:

4.4、poll 删除堆顶元素
删除堆顶元素,poll 源码:



可以看到源码的向下调整的方法,与我们实现的方法大致相同。
五、OJ练习
5.1、top-k 问题:最小的 k 个数
面试题 17.14. 最小K个数 - 力扣(LeetCode)
- 解法1:用排序算法,从小到大排序,取前 k 个。最快的排序算法时间复杂度 O(NlogN)。
- 解法2:创建小根堆,取 k 次堆顶元素,每次删除堆顶元素后,向下调整。时间复杂度 O(NlogN)。
/*** 方法1:使用小根堆实现topK小,* 时间复杂度:O(NlogN) + O(k*logN) = O(NlogN)*/public static int[] topK1(int[] arr, int k) {if(k > arr.length) { // k大于数组长度,直接返回数组return arr;}if(k <= 0) { // k小于等于0,返回空数组return new int[0];}// 创建一个小根堆,O(NlogN)PriorityQueue<Integer> pq = new PriorityQueue<>(k);for (int x : arr) {pq.offer(x);}// 取k次堆顶元素,存入数组,O(k*logN)int[] ret = new int[k];for (int i = 0; i < k; i++) {ret[i] = pq.poll();}return ret;}
- 解法三:创建大小为 k 的大根堆,从 k 开始遍历数组,遇到比堆顶(堆中的最大值)小的 x,就删除堆顶,插入 x。效率最高,O(N*logk)。
/*** 方法2:使用大根堆实现topK小,* 时间复杂度:O(k*logk) + O((N-k)*logk) + O(k*logk) = O(N*logk)*/public static int[] topK2(int[] arr, int k) {if(k > arr.length) { // k大于数组长度,直接返回数组return arr;}if(k <= 0) { // k小于等于0,返回空数组return new int[0];}// 自定义比较器,实现大根堆Comparator<Integer> cmp = new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}};// 创建一个大小为 k 的大根堆,O(k*logk)PriorityQueue<Integer> pq = new PriorityQueue<>(k, cmp);for (int i = 0; i < k; i++) {pq.offer(arr[i]);}// 遍历数组,如果元素小于堆顶元素,替换堆顶元素,并调整堆,O((N-k)logk)for (int i = k; i < arr.length; i++) {if (arr[i] < pq.peek()) {pq.poll();pq.offer(arr[i]);}}// 取出堆中的元素,存入数组,O(k*logk)int[] ret = new int[k];for (int i = 0; i < k; i++) {ret[i] = pq.poll();}return ret;}
5.2、堆排序
将序列从小到大排序:
- 方法1:创建小根堆,每次取堆顶,放入一个新的数组中。
空间复杂度:O(N),创建了一个新数组存放结果。当数据很多时,浪费内存。
时间复杂度:O(NlogN)。
- 方法2:创建大根堆,执行 N-1 次循环,每次将堆顶最大元素与未排序的堆尾元素交换,交换后对未排序的部分进行调整。
空间复杂度:O(1)
时间复杂度:O(NlogN)
初始状态:

第一次交换,并调整:

第二次交换,并调整:

// 堆排序public void sort() {for (int i = usedSize-1; i > 0; i--) {// 交换堆顶和最后一个未排序的元素swap(0, i);// 向下调整堆,元素 i-1 已排序shiftDown(0, i-1);}}相关文章:
【数据结构】(9) 优先级队列(堆)
一、优先级队列 优先级队列不同于队列,队列是先进先出,优先级队列是优先级最高的先出。一般有两种操作:返回最高优先级对象,添加一个新对象。 二、堆 2.1、什么是堆 堆也是一种数据结构,是一棵完全二叉树,…...
4、IP查找工具-Angry IP Scanner
在前序文章中,提到了多种IP查找方法,可能回存在不同场景需要使用不同的查找命令,有些不容易记忆,本文将介绍一个比较优秀的IP查找工具,可以应用在连接树莓派或查找IP的其他场景中。供大家参考。 Angry IP Scanner下载…...
【Linux】命令操作、打jar包、项目部署
阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 一:Xshell下载 1:镜像设置 二:阿里云设置镜像Ubuntu 三…...
瑞萨RA-T系列芯片ADCGPT功能模块的配合使用
在马达或电源工程中,往往需要采集多路AD信号,且这些信号的优先级和采样时机不相同。本篇介绍在使用RA-T系列芯片建立马达或电源工程时,如何根据需求来设置主要功能模块ADC&GPT,包括采样通道打包和分组,GPT触发启动…...
python爬虫系列课程1:初识爬虫
python爬虫系列课程1:初识爬虫 一、爬虫的概念二、通用爬虫和自定义爬虫的区别三、开发语言四、爬虫流程一、爬虫的概念 网络爬虫(又被称为网页蜘蛛、网络机器人)就是模拟浏览器发送网络请求,接收请求响应,一种按照一定的规则,自动抓取互联网信息的程序。原则上,只要是…...
【笔记】Huggingface Transformers 库加载预训练模型的 4 种方式
Transformers 库加载预训练模型的 4 种方式 Hugging Face Transformers 库提供了 4 种核心代码范式用于加载预训练大语言模型(LLM),具体分类如下: 通用模型加载(无任务头) 使用 AutoModel 加载基础架构&a…...
Unity Shader学习6:多盏平行光+点光源 ( 逐像素 ) 前向渲染 (Built-In)
0 、分析 在前向渲染中,对于逐像素光源来说,①ForwardBase中只计算一个平行光,其他的光都是在FowardAdd中计算的,所以为了能够渲染出其他的光照,需要在第二个Pass中再来一遍光照计算。 而有所区别的操作是࿰…...
tailwindcss学习01
系列教程 01 入门 02 vue中接入 入门 # 注意使用cmd不要powershell npm init -y # 如果没有npx则安装 npm install -g npx npm install -D tailwindcss3.4.17 --registry http://registry.npm.taobao.org npx tailwindcss init修改tailwind.config.js /** type {import(tai…...
DIN:引入注意力机制的深度学习推荐系统,
实验和完整代码 完整代码实现和jupyter运行:https://github.com/Myolive-Lin/RecSys--deep-learning-recommendation-system/tree/main 引言 在电商与广告推荐场景中,用户兴趣的多样性和动态变化是核心挑战。传统推荐模型(如Embedding &…...
【前端】如何安装配置WebStorm软件?
文章目录 前言一、前端开发工具WebStorm和VS Code对比二、官网下载三、安装1、开始安装2、选择安装路径3、安装选项4、选择开始菜单文件夹5、安装成功 四、启动WebStorm五、登录授权六、开始使用 前言 WebStorm 是一款由 JetBrains 公司开发的专业集成开发环境(IDE…...
【Golang学习之旅】Go 语言微服务架构实践(gRPC、Kafka、Docker、K8s)
文章目录 1. 前言:为什么选择Go语言构建微服务架构1.1 微服务架构的兴趣与挑战1.2 为什么选择Go语言构建微服务架构 2. Go语言简介2.1 Go 语言的特点与应用2.2 Go 语言的生态系统 3. 微服务架构中的 gRPC 实践3.1 什么是 gRPC?3.2 gRPC 在 Go 语言中的实…...
Spring核心思想之—AOP(面向切面编程)
目录 一 .AOP概述 二. Spring AOP 使用 2.1 引入AOP依赖 2.2 编写AOP程序 三. Spring AOP详情 3.1 切点(Pointcut) 3.2 连接点(Join Point) 3.3通知(Advice) 3.4切面(Aspect) 3.5通知 3.6 PointCut (公共切点)…...
使用 Openpyxl 操作 Excel 文件详解
文章目录 安装安装Python3安装 openpyxl 基础操作1. 引入2. 创建工作簿和工作表3. 写入数据4. 保存工作簿5. 加载已存在的Excel6. 读取单元格的值7. 选择工作表 样式和格式化1. 引入2. 设置字体3. 设置边框4. 填充5. 设置数字格式6. 数据验证7. 公式操作 性能优化1. read_only/…...
关于使用雪花算法生成唯一ID,返回给前端ID不一致的问题
问题 在某个项目中,使用雪花算法生成的唯一ID,从数据库查询到数据后返回给前端,但是前端接受到的数据ID和数据库原先生成的不一致 但是前端展示的数据: 原因 原因是后端使用Long类型来存储雪花算法生成的ID,但是这个数值已经超过前端数值类型的范围,导致前端在存储这个数值…...
axios post请求 接收sse[eventsource]数据的
axios 接收sse数据的 axios 接收sse数据的 EventSource什么 基于 HTTP 协议实现,通过与服务器建立一个持续连接,实现了服务器向客户端推送事件数据的功能。在客户端,EventSource 对象通过一个 URL 发起与服务器的连接。连接成功后࿰…...
Spring Boot 示例项目:从零开始构建 Web 应用
一、项目概述 本文档将指导您通过一个示例项目,了解如何使用 Spring Boot 框架构建一个简单的 Web 应用程序。该项目涵盖了从数据模型定义到控制器、服务层以及数据访问层的完整开发流程,帮助您快速掌握 Spring Boot 的基本使用方法。 二、项目结构 1. 项目模块 本示例项…...
大语言模型常用微调与基于SFT微调DeepSeek R1指南
概述 大型语言模型(LLM,Large Language Model)的微调(Fine-tuning)是指在一个预训练模型的基础上,使用特定领域或任务的数据对模型进行进一步训练,以使其在该领域或任务上表现更好。微调是迁移…...
聚焦地灾防治,助力城市地质安全风险防控
城市是人类社会发展的重要载体,承载着经济繁荣、文化交流和人口聚集等重要功能。然而,由于城市建设过程中地质条件复杂,地质灾害风险隐患存在,城市地质安全等问题日益突出,引起人们的广泛关注。为保障城市发展的安全和…...
为什么WP建站更适合于谷歌SEO优化?
在当今数字时代,建立一个网站似乎变得容易,但要构建一个真正能够带来流量和订单的网站却并非易事。特别是在谷歌SEO优化方面,不同的建站程序在SEO支持方面的效果差异显著。对于希望提升搜索引擎表现的用户来说,WordPress无疑是最佳…...
基于JavaScript的实时数据监控仪表盘开发实践
基于JavaScript的实时数据监控仪表盘开发实践 一、项目背景 某云计算服务商需要为其客户提供服务器集群健康状态监控系统。原有系统存在以下痛点: 数据刷新依赖手动操作可视化效果单一(仅表格展示)缺乏异常状态的智能预警移动端适配性差 …...
同步异步日志系统-日志落地模块的实现
功能:将格式化完成后的日志消息字符串,输出到指定的位置 扩展:支持同时将日志落地到不同的位置 位置分类: 1.标准输出 2.指定文件(时候进行日志分析) 3.滚动文件(文件按照时间/大小进行滚动…...
大模型常识:什么是大模型/大语言模型/LLM
本文原创作者:姚瑞南 AI-agent 大模型运营专家,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。(转载需经授权) 目录 一、什么是语言模型? 那么什么是语言模…...
用deepseek学大模型08-长短时记忆网络 (LSTM)
deepseek.com 从入门到精通长短时记忆网络(LSTM),着重介绍的目标函数,损失函数,梯度下降 标量和矩阵形式的数学推导,pytorch真实能跑的代码案例以及模型,数据, 模型应用场景和优缺点,及如何改进解决及改进方法数据推导…...
IOT通道MQTT
IoT通道是物联网(IoT)系统中用于设备与云端或设备之间通信的专用通道,其主要作用是实现数据的高效传输和设备的远程控制。以下是关于IoT通道的定义、应用和技术特点的总结: 定义 IoT通道是物联网设备与云端或设备之间建立的通信…...
(蓝桥杯——10. 小郑做志愿者)洛斯里克城志愿者问题详解
题目背景 小郑是一名大学生,她决定通过做志愿者来增加自己的综合分。她的任务是帮助游客解决交通困难的问题。洛斯里克城是一个六朝古都,拥有 N 个区域和古老的地铁系统。地铁线路覆盖了树形结构上的某些路径,游客会询问两个区域是否可以通过某条地铁线路直达,以及有多少条…...
小胡说技书博客分类(部分目录):服务治理、数据治理与安全治理对比表格
文章目录 一、对比表格二、目录2.1 服务2.2 数据2.3 安全 一、对比表格 下表从多个维度对服务治理、数据治理和安全治理进行详细对比,为读者提供一个直观而全面的参考框架。 维度服务治理数据治理安全治理定义对软件开发全流程、应用交付及API和接口管理进行规范化…...
开源模型应用落地-DeepSeek-R1-Distill-Qwen-7B-LoRA微调-LLaMA-Factory-单机单卡-V100(一)
一、前言 如今,大语言模型领域热闹非凡,各种模型不断涌现。DeepSeek-R1-Distill-Qwen-7B 模型凭借其出色的效果和性能,吸引了众多开发者的目光。而 LLaMa-Factory 作为强大的微调工具,能让模型更好地满足个性化需求。 在本篇中&am…...
如何避免redis长期运行持久化AOF文件过大的问题:AOF重写
一、AOF 重写的核心作用 通过 重建 AOF 文件,解决以下问题: 体积压缩:消除冗余命令(如多次修改同一 key),生成最小操作集合。混合持久化支持(若启用 aof-use-rdb-preamble yes)&am…...
uni-app发起网络请求的三种方式
uni.request(OBJECT) 发起网络请求 具体参数可查看官方文档uni-app data:请求的参数; header:设置请求的 header,header 中不能设置 Referer; method:请求方法; timeout:超时时间,单位 ms&a…...
以下是一个使用 HTML、CSS 和 JavaScript 实现的登录弹窗效果示例
以下是一个使用 HTML、CSS 和 JavaScript 实现的登录弹窗效果示例: <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>登录弹窗示例</title><style>body {font-family: Aria…...
