数据结构——优先级队列(PriorityQueue)

article/2025/8/25 13:52:06

1.优先级队列

优先级队列可以看作队列的另一个版本,队列的返回元素是由是由插入顺序决定的,先进先出嘛,但是有时我们可能想要返回优先级较高的元素,比如最大值?这种场景下就由优先级队列登场。
优先级队列底层是由堆实现的,可以说理解了堆就理解了优先级队列。

2.堆的概念

堆可以看作一颗完全二叉树。它将数据以完全二叉树的顺序存储在数组中。堆又分为大根堆小根堆,如果以完全二叉树的方式看待大小根堆。
大根堆就是对于这棵树及该树的每一棵子树都满足左右子树节点值小于根节点值
小根堆就是对于这棵树及该树的每一棵子树都满足左右子树节点值大于根节点值
具体图如下:
在这里插入图片描述

3.实现堆

堆是在一维数组中存储的,所以我们首先需要一个数组去存储我们堆中数据,然后我们还可以通过变量usedSize去表示当前堆中已有元素,具体代码如下:

public class MyHeap {private int [] elem;private int usedSize;public  MyHeap(){//构造方法this.elem=new int[10];this.usedSize=0;}}

为方便演示,我们直接将参数数组中元素“复制”给堆中对应元素。具体代码如下:

public void initHeap(int [] array){for (int i = 0; i <array.length ; i++) {elem[i]=array[i];usedSize++;}}
//运行结果:MyHeap{elem=[10, 26, 98, 75, 15, 64, 85, 32, 12, 100], usedSize=10}

显然此时的堆只能看做为一个数组,其元素按照完全二叉树顺序排列如下图,既不是大根堆也不是小根堆。
在这里插入图片描述

我们要让它变成一个堆就必须对其元素调整,本文演示调整大根堆,那么,如何调整为大根堆?
我们的思路是:从最后一颗子树开始,一颗一颗调整直到根节点,那么对于每一棵树:我们先找到其左右节点的最大值,然后拿着这个最大值跟父节点比较,比父节点大就交换,不如它大就不动。如何找到最后一棵子树:最后一个叶节点对应的父节点,若父节点为p,子节点为i,则有i=2*p+1,p=(i-1)/2。叶子节点下标为usedSize-1,那么其父节点下标就是(usedSize-1-1)/2。所以我们可以写个for循环去遍历调整每棵二叉树。存在一种情况:我在交换父节点和子节点值之后的堆依然不符合大根堆规则,比如上图中75和26换,换完之后26显然比32小,所以还需要再进行交换,我们可以在一次交换过后让parent=child;child=2*parent+1这样继续往下调整,但是我们也不能不限制的往下递归,所以需要一个结束条件,那就是child<usedSize。具体代码如下:

    for(int parent=(usedSize-2)/2;parent>=0;parent--){siftDown(usedSize,parent);}}//针对每一棵树的调整public void siftDown(int usedSize,int parent){int child=2*parent+1;while(child<usedSize){if(child+1<usedSize&&elem[child]<elem[child+1]){child++;}//child指向较大if(elem[child]>elem[parent]){int tmp=elem[child];elem[child]=elem[parent];elem[parent]=tmp;//调整值parent=child;child=2*parent+1;}else{break;}}

offer

作用:此方法用于向堆中添加元素
思路:首先判断堆满没,满了扩容,然后将元素插入到堆中末尾,随后向上调整,将该元素调整到合适位置。
由于usedSize我们可以在类中直接访问到,所以我们push之后的调整只传一个当前末尾节点的下标。然后根据此下标推算出其父节点。若父节点为p,子节点为i,则有i=2*p+1,p=(i-1)/2,如果插入节点大于其父节点就交换,然后child=parent,parent=(child-1)/2,注意此时parent是一级一级往上走的,所以这种调整方式也叫向上调整,同理,刚才那个parent一级一级往下走的就是向下调整。直到根节点遍历完毕或者该插入节点遇到了比它大的父节点就结束循环。具体代码如下:

 public void push(int val){if(isFull()){elem=Arrays.copyOf(elem,2*elem.length);}elem[usedSize]=val;signUp(usedSize);//这里usedSize作为末尾节点下标传进去usedSize++;}private void signUp(int child) {int parent=(child-1)/2;while(parent>-1){if(elem[parent]<elem[child]){//交换int tmp=elem[parent];elem[parent]=elem[child];elem[child]=tmp;//调整child=parent;parent=(child-1)/2;}else {break;}}}private boolean isFull() {return usedSize==elem.length;}

2.2poll

作用:此方法用于拿出堆顶元素
思路:首先将堆顶元素与堆末元素交换,随后将usedSize减一,也就是逻辑上删除了它,等下次push就会覆盖它,但是交换后的堆一定不符合堆的性质,所以还需sfitDown将堆调整为正确的堆。具体代码如下:

 public int poll(){int val=elem[0];int tmp=elem[0];elem[0]=elem[usedSize-1];elem[usedSize-1]=tmp;usedSize--;siftDown(usedSize,0);return val;}

3.优先级队列的基本特性

1.优先级队列中的元素必须是能比较大小的,不然会抛出ClassCastException异常。
2.优先级队列中不能插入null对象,不然会抛出NullPointerException。
3.优先级队列默认实现小根堆。

3.1如何改为大根堆

 PriorityQueue<Integer> queue1=new PriorityQueue<>(Comparator.reverseOrder());

4.top-k问题

给定一组数据,求前K个最大/小的数据。
题目链接:https://leetcode.cn/problems/smallest-k-lcci
思路1:利用优先级队列的特性。看到那句 以任意顺序 我就知道这道题是为优先级队列量身定做的。具体代码实现:

     public int[] smallestK(int[] arr, int k) {PriorityQueue<Integer> queue = new PriorityQueue<>();//非空校验int[] answer = new int[k];//根据题意,注意初始化大小一定要和返回数据数量一致if (arr == null || k <= 0) {int [] a=new int[0];return a;}for(int i=0;i<arr.length;i++){queue.offer(arr[i]);}for(int i=0;i<k;i++){answer[i]=queue.poll();}return answer;}

思路2:用堆排序。
如果求前K个最大值,我们就建小根堆。
如果求前K个最小值,我们就建大根堆。

本题求最小值,我们建大根堆,先把K个数据放到堆中,然后假定它们就是要返回的数据,因为我建的是大根堆,堆顶元素一定是目前堆中最小元素的“最大者”,可以把它理解为“门槛”,然后遍历剩余元素与这个“门槛”比较,如果比“门槛”也就是当前堆顶元素小那我就删除当前堆顶元素同时将这个元素插入进堆,然后继续遍历直到结束,最后返回堆中元素即可。
求最大值思路类似,我建小根堆这样堆顶元素就是当前堆中元素的最小值,如果你比堆顶元素大你就可以入堆,原来的元素被删除。(成王败寇这一块/.)
本题具体代码如下:

public int[] smallestK(int[] arr, int k) {if(arr==null||k<=0){int [] a=new int[0];return a;}PriorityQueue<Integer> queue=new PriorityQueue<>(Comparator.reverseOrder());for(int i=0;i<k;i++){queue.offer(arr[i]);}for(int i=k;i<arr.length;i++){//注意这里i从k开始遍历int peekVal=queue.peek();if(peekVal>arr[i]){queue.poll();queue.offer(arr[i]);}else{continue;}}int [] answer=new int[k];for(int i=0;i<k;i++){answer[i]=queue.poll();}return answer;}

That’s all.


http://www.hkcw.cn/article/APjeEQaMox.shtml

相关文章

学习如何设计大规模系统,为系统设计面试做准备!

前言 在当今快速发展的技术时代&#xff0c;系统设计能力已成为衡量一名软件工程师专业素养的重要标尺。随着云计算、大数据、人工智能等领域的兴起&#xff0c;构建高性能、可扩展且稳定的系统已成为企业成功的关键。然而&#xff0c;对于许多工程师而言&#xff0c;如何有效…

负载电容匹配:晶振电路设计中被忽视的隐形杀手

在电子电路的复杂世界里&#xff0c;晶振电路作为频率控制的核心部件&#xff0c;其稳定性和准确性对整个系统的性能起着举足轻重的作用。晶振就如同电子设备的“心脏起搏器”&#xff0c;精准地控制着电路的运行节奏。然而&#xff0c;在众多影响晶振电路性能的因素中&#xf…

Python Day36 学习

对列表、字典、元组、集合进行总结 浙大疏锦行 摘自讲义 机器学习管道Pipeline Q1. 什么是机器学习管道Pipeline&#xff1f; 摘自讲义 Q. 关于“转换器”&#xff1f; 摘自讲义 # 导入StandardScaler转换器 from sklearn.preprocessing import StandardScaler# 初始化转换…

003 flutter初始文件讲解(2)

1.书接上回 首先&#xff0c;我们先来看看昨天最后的代码及展示效果&#xff1a; import "package:flutter/material.dart";void main(){runApp(MaterialApp(home:Scaffold(appBar:AppBar(title:Text("The World")), body:Center(child:Text("Hello…

深入理解C#中的LINQ:数据查询的终极利器

在现代软件开发中&#xff0c;数据处理和查询是几乎所有应用程序的核心需求。无论是从数据库检索数据、过滤内存中的集合&#xff0c;还是解析XML文档&#xff0c;开发者都需要高效、灵活的方式来操作数据。C# 提供的 LINQ&#xff08;Language Integrated Query&#xff0c;语…

133.在 Vue3 中使用 OpenLayers 实现画多边形、任意编辑、遮罩与剪切处理功能

&#x1f3ac; 效果演示截图&#xff08;先睹为快&#xff09; ✨ 功能概览&#xff1a; ✅ 鼠标画任意形状多边形&#xff1b; ✏️ 点击“修改边界”可拖动顶点&#xff1b; &#x1f7e5; 点击“遮罩”后地图除多边形区域外变红&#xff1b; ✂️ 点击“剪切”后仅显示选…

爬虫到智能数据分析:Bright Data × Kimi 智能洞察亚马逊电商产品销售潜力

前言 电商数据分析在现代商业中具有重要的战略价值&#xff0c;通过对消费者行为、销售趋势、商品价格、库存等数据的深入分析&#xff0c;企业能够获得对市场动态的精准洞察&#xff0c;优化运营决策&#xff0c;预测市场趋势、优化广告投放、提升供应链效率&#xff0c;并通…

2025年信息素养大赛 图形化编程复赛 官方样题绘制图形答案解析

今天给大家做一下2025年全国青少年信息素养大赛 图形化编程复赛、决赛官方样题1 编程题&#xff0c;绘制图形及答案解析。 题外话&#xff1a;2024年对Scratch画笔画图考的比较多&#xff0c;例如7月20日的复赛小高组就考了4道数形结合的画图编程题&#xff0c;点击查看&#x…

ONLYOFFICE文档API:编辑器的品牌定制化

在当今数字化办公时代&#xff0c;文档编辑器已成为各类企业、组织和开发者不可或缺的工具之一。ONLYOFFICE 文档提供的功能丰富且强大的文档编辑 API&#xff0c;让开发者能够根据自己的产品需求和品牌特点&#xff0c;定制编辑器界面&#xff0c;实现品牌化展示&#xff0c;为…

【unity游戏开发——编辑器扩展】EditorApplication公共类处理编辑器生命周期事件、播放模式控制以及各种编辑器状态查询

注意&#xff1a;考虑到编辑器扩展的内容比较多&#xff0c;我将编辑器扩展的内容分开&#xff0c;并全部整合放在【unity游戏开发——编辑器扩展】专栏里&#xff0c;感兴趣的小伙伴可以前往逐一查看学习。 文章目录 前言一、监听编辑器事件1、常用编辑器事件2、示例监听播放模…

企业如何制定互联网营销策略?

互联网环境的变化速度&#xff0c;让很多企业不懂得在这个流量时代该如何更好地抓住推广时机。企业在制定互联网营销策略的过程中&#xff0c;该如何让策略能够成功生效&#xff0c;令其为企业发展赋能呢&#xff1f;下面就让我们分四步来简单了解下。 一、明确品牌定位 在制定…

Windows10下搭建sftp服务器(附:详细搭建过程、CMD连接测试、连接失败问题分析解决等)

最终连接sftp效果 搭建sftp服务器 1、这里附上作者已找好的 freeSSHd安装包 ,使用它进行搭建sftp服务器。 2、打开freeSSHd安装包,进行安装 (1)、选择完全安装 (2)、安装完成后,对提示窗口选择关闭 (3)、安装完成后,提示是否安装私有密钥。我们选择"是" (4)、安…

第五十九节:性能优化-GPU加速 (CUDA 模块)

在计算机视觉领域,实时性往往是关键瓶颈。当传统CPU处理高分辨率视频流或复杂算法时,力不从心。本文将深入探索OpenCV的CUDA模块,揭示如何通过GPU并行计算实现数量级的性能飞跃。 一、GPU加速:计算机视觉的必由之路 CPU的强项在于复杂逻辑和低延迟任务,但面对图像处理中高…

Linux---系统守护systemd(System Daemon)

一、systemd 概述 1. 定位与作用 init 系统替代品&#xff1a;作为 Linux 系统的第 1 个进程&#xff08;PID1&#xff09;&#xff0c;替代传统的 SysVinit 和 Upstart&#xff0c;负责管理系统服务、启动流程、资源分配等。统一管理&#xff1a;通过 单元&#xff08;Unit&…

Lua语言学习

为什么要用Lua 大部分的手机系统出于安全考虑禁止从网络上下载代码后动态的将这些下载的代码加载到内存中执行 所以&#xff0c;当你更新游戏时&#xff0c;就必须让用户从手机市场下载更新版本的程序&#xff0c;游戏程序通常体积较大&#xff0c;重新下载不仅耗时还耗流量&…

Maven 仓库类型与镜像策略

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

蓝牙和wifi相关的杂项内容总结

蓝牙的传输速率演进 蓝牙技术的传输速率随着版本的演进不断提升&#xff0c;不同版本和模式&#xff08;经典蓝牙 BR/EDR 和低功耗蓝牙 BLE&#xff09;的速率差异显著。以下是蓝牙传输速率的完整发展历程和技术细节&#xff1a; 经典蓝牙&#xff08;BR/EDR&#xff09;的速…

AAA稳态LED太阳光模拟器的特点剖析

AAA稳态LED太阳光模拟器作为光伏测试领域的重要设备&#xff0c;其技术特点直接关系到太阳能电池研发与质量控制的精度。以下从光谱匹配性、辐照均匀性、稳定性、能效比及智能化设计五个维度展开深度剖析&#xff1a; 一、光谱匹配性的突破性进展 传统氙灯光源在AM1.5G标准光谱…

cadence PCB 精度设置成小数点4位方法

1. allegro 在进行PCB设计时&#xff0c;单位一般默认为Mils&#xff0c;会遇到&#xff0c;精度只能选择2位&#xff0c;不能增加到4位&#xff0c; 精度的范围只能设置为0-2&#xff0c;不能设置为3或4 2. Setup -> User preference&#xff0c;进行设置&#xff0c…

VirtualBox安装 Rocky

这不是 CentOS要完蛋了吗&#xff0c;找了Rock Linux 。下载了一个差不多需要10G&#xff0c;艹。 然后在virtual BOX中安装&#xff0c;安装成功了 安装和Centos一样&#xff1a; 《VirtualBox安装以及安装CentOS7》 有几点需要注意就行了&#xff1a; 准备工作 确保主机的…