数据结构---优先级队列(堆)

news2025/1/6 18:44:11

 博主主页: 码农派大星.

数据结构专栏:Java数据结构

关注博主带你了解更多数据结构知识


1. 优先级队列

1.1 概念

前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队 列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话.

在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数 据结构就是优先级队列(Priority Queue)。

1.2 优先级队列的模拟实现

JDK1.8中的PriorityQueue底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上进行了一些调整。

2.堆的概念

2.1堆的概念

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆),即根节点小于左子树和右子树则为小即每一个将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:

1.堆中某个节点的值总是不大于或不小于其父节点的值;

2.堆总是一棵完全二叉树。

 2.2 堆的存储方式      

堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储, 

注意:

对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低。 

将元素存储到数组中后,可以根据二叉树的性质对树进行还原。假设i为节点在数组中的下标,则有

如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2

如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子

如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子

2.3 堆的创建 

以大根堆为例:

向下调整:从最后一个父节点:(usedsize-1-1)/2的位置开始到0,到0位置结束。向下遍历时候如果父节点大于孩子节点就交换

插入元素的时候,插入到最后一个再向上调整

出元素的时候,出的是最大的元素,那么久直接让坐标0的元素和最后一个元素进行交换,再向下调整。

import java.util.Arrays;
public class TestHeap {
    public int[] elem;
    public int usedSize;
    public TestHeap() {
        this.elem = new int[10];
    }
    public void init(int[] array) {
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
    }
    //把elem数组当中的数据 调整为大根堆  
    public void createHeap() {
        for (int parent = (usedSize - 1 - 1) / 2; parent >= 0; parent--) {
            siftDown(parent, usedSize);
        }
    }
    private void swap(int i, int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }
    public void siftDown(int parent, int end) {
        int child = 2 * parent + 1;
        while (child < end) {
            if (child + 1 < end && elem[child] < elem[child + 1]) {
                child++;
            }
            //child下标 就是 左右孩子的最大值
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }
     public boolean isFull(){
        return usedSize == elem.length;
    }
    //插入
    public void offer(int val){
        if(isFull()){
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize] = val;
        usedSize++;
        siftUp(usedSize-1);
    }
    public void siftUp(int child){
        int parent = (child-1)/2;
        while (parent >= 0){
            if(elem[child] > elem[parent]){
                swap(child,parent);
                child = parent;
                parent = (child-1)/2;
            }else {
                break;
            }
        }
    }
    public boolean isEmpty(){
        return usedSize == 0;
    }
    //删除
    public int poll(){
        if(isEmpty()){
            return -1;
        }
        int old = elem[0];
        swap(0,usedSize-1);
        usedSize++;
        siftDown(0,usedSize);
        return old;
    }
}

3.PriorityQueue

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线 程不安全的,PriorityBlockingQueue是线程安全的,本文主要介绍PriorityQueue。

PriorityQueue的使用要注意:

1. 使用时必须导入PriorityQueue所在的包

import java.util.PriorityQueue;

2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException异常

3. 不能插入null对象,否则会抛出NullPointerException

4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容

5. 插入和删除元素的时间复杂度为O(\log_{2}N)

6. PriorityQueue底层使用了堆数据结构

7. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素

3.1PriorityQueue常用接口介绍

static void TestPriorityQueue(){
 // 创建一个空的优先级队列,底层默认容量是11
 PriorityQueue<Integer> q1 = new PriorityQueue<>();


 // 创建一个空的优先级队列,底层的容量为initialCapacity
 PriorityQueue<Integer> q2 = new PriorityQueue<>(100);


 ArrayList<Integer> list = new ArrayList<>();
 list.add(4);
 list.add(3);
 list.add(2);
 list.add(1);
 // 用ArrayList对象来构造一个优先级队列的对象
 // q3中已经包含了三个元素
 PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
 System.out.println(q3.size());
 System.out.println(q3.peek());
}

注意:默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器

3.2. 插入/删除/获取优先级最高的元素

static void TestPriorityQueue2(){
int[] arr = {4,1,9,2,8,0,7,3,6,5};
// 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好
// 否则在插入时需要不多的扩容
// 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低
PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
for (int e: arr) {
q.offer(e);
}
System.out.println(q.size()); // 打印优先级队列中有效元素个数
System.out.println(q.peek()); // 获取优先级最高的元素
// 从优先级队列中删除两个元素之和,再次获取优先级最高的元素
q.poll();
q.poll();
System.out.println(q.size()); // 打印优先级队列中有效元素个数
System.out.println(q.peek()); // 获取优先级最高的元素
q.offer(0);
System.out.println(q.peek()); // 获取优先级最高的元素
// 将优先级队列中的有效元素删除掉,检测其是否为空
q.clear();
if(q.isEmpty()){
System.out.println("优先级队列已经为空!!!");
}
else{
System.out.println("优先级队列不为空");
}
}

 3.3 PriorityQueue构建大根堆

创建大根堆,只需要构造一个比较器,重写Compare方法就可以了

//构造比较器:
class IntCmp implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2-o1;
    }
    public class TestPriorityQueue {
    public static void main(String[] args) {
        PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
        p.offer(4);
        p.offer(3);
        p.offer(2);
        p.offer(1);
        p.offer(5);
         System.out.println(p.peek());
    }
}

3.4PriorityQueue的扩容方式

优先级队列的扩容说明:

如果容量小于64时,是按照oldCapacity的2倍方式扩容的

如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的

如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容 

4.top-k问题 OJ链接

1.前k个建堆,建立大小为k的大根堆,把下标为k的元素与堆顶元素比较

2.如果小于堆顶元素,堆顶元素出堆,把此元素放入堆中,k++,再循环遍历完数组

import java.util.PriorityQueue;
class IntCmp implements Comparator<Integer>{
    @Override
     public int compare(Integer o1, Integer o2) {
      return o2-o1;
    }
}
class Solution {
    public int[] smallestK(int[] arr, int k) {
         int [] tmp = new int [k];
         if(k == 0){
            return tmp;
         }
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new IntCmp());
        //1,把前K个元素放入堆中

        for(int i = 0; i<k; i++){
            maxHeap.offer(arr[i]);
        }
        //2.遍历剩下的N-K个元素
        for(int i = k ; i<arr.length;i++){
            int val = maxHeap.peek();
            if(val > arr[i]){
                maxHeap.poll();
                maxHeap.offer(arr[i]);
            }
        }
       
        for(int i = 0; i< k; i++){
            tmp[i] = maxHeap.poll();
        }
        return tmp;


    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Rust 赋能前端 -- 写一个 File 转 Img 的功能

所有耀眼的成绩,都需要苦熬,熬得过,出众;熬不过,出局 大家好,我是柒八九。一个专注于前端开发技术/Rust及AI应用知识分享的Coder 此篇文章所涉及到的技术有 Rustwasm-bindgen/js-sys/web-sysWeb WorkerWebAssemblyWebpack/Vite配置WebAssemblyOffscreenCanvas脚手架生成项…

win11缺少msvcr110dll,msvcp110.dll的解决办法

MSVCP110.dll是一个与Microsoft Visual C 2012 Redistributable Package相关的动态链接库&#xff08;Dynamic Link Library&#xff09;文件&#xff0c;主要用于支持使用C编写的Windows应用程序运行时所需的特定功能。当用户尝试运行依赖于这个库的应用程序时&#xff0c;如果…

【网络安全】网络安全协议的重要性

一.网络安全 1.什么是网络安全 网络安全&#xff08;Cyber Security&#xff09;是指网络系统的硬件、软件及其系统中的数据受到保护&#xff0c;不因偶然的或者恶意的原因而遭受到破坏、更改、泄露&#xff0c;系统连续可靠正常地运行&#xff0c;网络服务不中断。 2.网络安…

智慧展厅设计的难点有哪些

1、运用先进的展示技术 将全息影像、三维投影、虚拟现实、人机互动等技术做做完美衔接&#xff0c;把展厅的内容展示做到丰富多彩&#xff0c;从而让展厅富有科技感和艺术性。 2、内容要生动有趣 从而更好地吸引参观者。展厅设计师要与客户有良好深入的沟通&#xff0c;搜集与整…

SAP 批量获取BOM中替代料信息(代码分享)

最近用户需要到导出BOM中存在替代料的信息,只要导出替代料的程序,但是使用展开BOM的程序执行后,导致执行时间很长,数量量也非常的大,内存溢出,程序就挂掉了。9万多个物料有BOM,当然不能让用户去导,后面我们写了一段SQL,用内表的方式给用户导出了需要的数据。 同时也找…

10.8k star,超好用的高颜值屏幕录制工具

最近公司需要给新来的同事做一些基础的培训。不过因为时间冲突&#xff0c;没办法现场给大家上课&#xff0c;所以老板让我自己在家把视频课程录制好&#xff0c;还说要让同事们看到我的样子。 这倒是有点费劲了&#xff0c;之前也录制过课程视频&#xff0c;但都是直接用屏幕…

小程序-修改用户头像

1、调用拍照 / 选择图片 // 修改头像 const onAvatarChange () > { // 调用拍照 / 选择图片 uni.chooseMedia({ // 文件个数 count: 1, // 文件类型 mediaType: [image], success: (res) > { console.log(res) // 本地临时文件路径 (本地路径) const { tempFilePath } …

【Tools】SpringBoot工程中,对于时间属性从后端返回到前端的格式问题

Catalog 时间属性格式问题一、需求二、怎么使用 时间属性格式问题 一、需求 对于表中时间字段&#xff0c;后端创建对应的实体类的时间属性需要设定格式&#xff08;默认的格式不方便阅读&#xff09;&#xff0c;再返回给前端。 二、怎么使用 导入jackson相关的坐标&#x…

PostgreSQL事务基础理解

PostgreSQL事务 事务是数据库管理系统执行过程中的一个逻辑单位&#xff0c;由一个有限的数据库操作序列构成。数据库事务通常包含一个序列对数据库的读和写操作&#xff0c;主要是包含以下两个目的&#xff1a; 为数据库操作序列提供一个从失败中恢复到正常状态的方法&#…

vue 打印、自定义打印、页面打印、隐藏页眉页脚

花了一天时间搞了个打印功能&#xff0c;现则将整体实现过程进行整理分享。先来看看效果图&#xff1a; 1、页面展示为&#xff1a; 2、重组页面打印格式为&#xff1a;这里重组页面的原因是客户要求为一行两列打印 &#xff01;内容过于多的行则独占一行显示完整。 整体实现&…

isscc2024 short course2 Performance Compute Environment

这部分分为4部分&#xff1a; 概览&#xff1a;LLMs和生成式AI 探讨大语言模型&#xff08;LLMs&#xff09;和生成式AI的整体环境&#xff0c;及其对硬件加速器设计的影响。 高性能AI加速器的特定考虑因素 广泛的模型和使用案例支持&#xff1a;需要设计能支持多种模型和应…

python中的线程并行

文章目录 1. 单线程2. 线程池ThreadPoolExecutor 1. 单线程 现在有1154张图片需要顺时针旋转后保存到本地&#xff0c;一般使用循环1154次处理&#xff0c;具体代码如下所示&#xff0c;img_paths中存储1154个图片路径&#xff0c;该代码段耗时约用97ms。 t1time.time() for …

SpringCloud系列(30)--准备使用Hystrix的前期工作,创建服务消费者模块

前言&#xff1a;在上一章节中我们创建了服务提供者模块&#xff0c;而本节内容则是创建服务消费者模块。 1、创建一个服务提供者模块&#xff0c;命名为cloud-consumer-feign-hystrix-order80 (1)在父工程下新建模块 (2)选择模块的项目类型为Maven并选择模块要使用的JDK版本 …

面向Prompt编程

Prompt 就像和一个人对话&#xff0c;你说一句&#xff0c;ta 回一句&#xff0c;你再说一句&#xff0c;ta 再回一句…… Prompt 就是你发给大模型的指令&#xff0c;比如「讲个笑话」、「用 Python 编个贪吃蛇游戏」、「给男/女朋友写封情书」等 貌似简单&#xff0c;但意义…

vue项目实战 - 如果高效的实现防抖和节流

在Vue项目中&#xff0c;处理高频事件的优化至关重要&#xff0c;直接影响用户体验和应用性能。防抖&#xff08;Debounce&#xff09;和节流&#xff08;Throttle&#xff09;是两种常用且有效的方法&#xff0c;可以控制事件触发频率&#xff0c;减少不必要的资源消耗。如何在…

labview_开放协议

一、开放协议 二、硬件设置 英格索兰硬件设置&#xff1a; 三、配套测试软件 四、Labview代码

科技赋能,打破视障人士的沟通壁垒

在探索如何增强盲人群体的社会参与度与幸福感的旅程中&#xff0c;盲人社交能力提升策略成为了不容忽视的一环。随着科技的不断进步&#xff0c;像“蝙蝠避障”这样的辅助软件&#xff0c;不仅在日常出行中为盲人提供了实时避障和拍照识别的便利&#xff0c;也在无形中为他们拓…

SQL面试题练习 —— 波峰波谷

来源&#xff1a;字节今日头条 目录 1 题目2 建表语句3 题解 1 题目 有如下数据&#xff0c;记录每天每只股票的收盘价格&#xff0c;请查出每只股票的波峰和波谷的日期和价格&#xff1b; 波峰定义&#xff1a;股票价格高于前一天和后一天价格时为波峰 波谷定义&#xff1a;股…

FPGA状态机设计详解

一.什么是状态机&#xff1f; 想象一下你正在玩一个电子游戏&#xff0c;角色有多种状态&#xff0c;比如“行走”、“跳跃”、“攻击”等。每当你按下不同的按键或者满足某些条件时&#xff0c;角色的状态就会改变&#xff0c;并执行与该状态对应的动作。这就是状态机的一个简…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-23.3,4,5,6 讲 I2C驱动-读取AP3216C传感器​

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…