Java数据结构之优先级队列

news2024/11/23 15:23:40

前言

总是忘了优先队列的一些应用,其实这都是很基础的了。为了再加强印象,抄一遍吧。

PriorityQueue简介

PriorityQueue,即优先级队列。优先级队列可以保证每次取出来的元素都是队列中的最小或最大的元素(Java优先级队列默认每次取出来的为最小元素)。

大小关系: 元素的比较可以通过元素本身进行自然排序,也可以通过构造方法传入比较器进行比较。

继承关系:

通过继承关系图可以知道PriorityQueue是Queen接口的一个实现类,而Queen接口是Collection接口的一个实现类,因此其拥有Collection接口的基本操作,此外,队列还提供了其他的插入,移除和查询的操作。每个方法存在两种形式:一种是抛出异常(操作失败时),另外一种是返回一个特殊值(null或者false,取决于操作)。

PriorityQueue的peek和element操作的时间复杂度都为常数,add、offer、remove以及poll的时间复杂度是log(n)。

PriorityQueue示例

import java.util.PriorityQueue;

public class PriorityQueueTest01 {
    public static void main(String[] args) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        queue.add(11);
        queue.add(33);
        queue.add(22);
        queue.add(55);
        queue.add(44);

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
    }
}

运行结果:

代码中我们依次添加11,33,22,55,44五个数据,然后进行删除,通过结果我们发现,每次删除的都为队列中最小元素,即体现了优先级队列。

结论: 优先级队列默认每次获取队列中最小的元素,也可以通过comparator比较器来自定义每次获取为最小还是最大。

注意: 优先级队列中不可以存储null。

Comparable比较器

Comparable接口

public interface Comparable<T> {
    public int compareTo(T o);
}

该接口只存在一个public int compareTo(T o);方法,该方法表示所在的对象和o对象进行比较,返回值分三种:

  • 1: 表示当前对象大于o对象

  • 0: 表示当前对象等于o对象

  • -1: 表示当前对象小于o对象

在优先级队列中或者具有比较特征的集合中存储的对象需要实现Comparable接口。

需求: 在优先级队列中存储对象学生,每个学生有id,name,age三个属性,并且使优先级队列每次按照学生的id从小到大取出。

代码示例:

Student类: 当前类实现了Comparable接口,即当前类提供了默认的比较方法。

public class Student implements Comparable{
    private int id;
    private String name;
    private int age;
    
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Object o) {
        Student o1 = (Student)o;
        return this.id - o1.id;
    }
}

PriorityQueueTest类:

public class PriorityQueueTest {
    public static void main(String[] args) {
        PriorityQueue<Student> queue = new PriorityQueue<>();
        queue.add(new Student(2,"王昭君",18));
        queue.add(new Student(1,"吕布",19));
        queue.add(new Student(5,"貂蝉",17));
        queue.add(new Student(3,"赵云",20));
        queue.add(new Student(4,"荆轲",18));

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
    }
}

运行结果:

Comparator比较器

新需求: 如果使优先级队列按照学生id从大到小取出呢?我们很快就会想到修改Student类的compareTo方法,使return o1.id - this.id;,这样当然可以实现我们的新需求。但是有很多时候类的compareTo方法是不能修改的,比如JDK给我们提供的源代码,在不修改compareTo方法的前提下实现需求,只能用Comparator比较器了。

Comparator接口

public interface Comparator<T> {
    int compare(T o1, T o2);
}

该接口中提供了int compare(T o1, T o2)方法,该方法需要参数是两个待比较的对象

返回结果是int类型

  • 1: 表示o1对象大于o2对象

  • 0: 表示o1对象等于o2对象

  • -1: 表名o1对象小于o2对象

修改PriorityQueueTest类:

import java.util.PriorityQueue;

public class PriorityQueueTest {
    public static void main(String[] args) {
        PriorityQueue<Student> queue = new PriorityQueue<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o2.getId() - o1.getId();
            }
        });
        
        queue.add(new Student(2,"王昭君",18));
        queue.add(new Student(1,"吕布",19));
        queue.add(new Student(5,"貂蝉",17));
        queue.add(new Student(3,"赵云",20));
        queue.add(new Student(4,"荆轲",18));

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
    }
}

运行结果:

底层原理

优先级队列是如何保证每次取出的是队列中最小(最大)的元素呢?查看源码,底层的存储结构为一个数组:

transient Object[] queue;

表面上是一个数组结构,实际上优先级队列采用的是堆的形式来进行存储的,通过调整小根堆或大根堆来保证每次取出的元素为队列中最小或最大。

小根堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值)

大根堆(任意一个非叶子节点的权值,都大于其左右子节点的权值)

可以通过数组来实现优先级队列底层实现,图示:

对于堆的实现是基于数组来实现的,实际开辟存储空间是数组,对数据的访问按照二叉树来进行访问遍历。父节点和子节点编号存在联系,父节点和子节点存在如下关系:

  • leftNo = parentNo * 2 + 1;

  • rightNo= parantNo * 2 + 2;

  • parentNo = (nodeNo - 1) / 2;

通过以上的三个公式,可以轻易的计算出某个节点的父节点以及子节点的下标,这就是为什么可以使用数组来存储堆的原因。

以小根堆为例,数据如何进行调整:

插入数据

图示:

插入数据首先在有效数据的最后一个位置,即插入在某个叶子节点上,以该节点为待调整节点,和其父节点比较,如果当前节点大于父节点,符合小根堆,不用进行调整,否则需要进行调整,调整至0号根节点或者是其中某一个位置时当前节点大于父节点才终止。

源码如下:

//从下往上调整过程  k表示待插入元素位置  x表示待插入数据
private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            //通过当前待调整节点k找到父节点
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            //如果此时父节点数据小于待插入节点数据,满足小根堆,break退出
            if (comparator.compare(x, (E) e) >= 0)
                break;
            //不满足小根堆,将父节点值插入待插入节点中
            queue[k] = e;
            //待比较位置就指向了父节点
            k = parent;
        }
        queue[k] = x;
    }

删除数据

图示:

因为是小根堆,其堆顶元素最小,所以删除的为堆顶的元素。删除堆顶元素过程,首先记录0号下标的位置,并用最后一个元素替换0号下标的元素,当前的小根堆可能被破坏,需要对堆进行调整,从k指定的位置开始,将逐层向下与当前的左右孩子中较小的进行交换,直到x小于或者等于左右孩子中的任何一个为止。

源码如下:

//删除节点 从上往下进行调整  待比较的位置,待调整的元素x
private void siftDownUsingComparator(int k, E x) {
        //将size/2 ,从上往下调整只需要比较到size/2即可,
        //因为size/2时已经到了叶子节点,无需再调整。
        int half = size >>> 1;
        while (k < half) {
            //找到当前节点的左孩子
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;//右孩子节点下标
            //找到左右孩子最小的节点,将位置记录到child,值记录在c上
            if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            //当左右孩子最小的都大于父节点x值,满足小根堆,结束调整过程
            if (comparator.compare(x, (E) c) <= 0)
                break;
            //最小的孩子节点小于父节点,不满足小根堆,需要进行调整
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

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

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

相关文章

微信小程序 if语法、for循环 条件渲染、列表渲染等讲解

这篇文章我想给大家学习的就是如何使用 if 去判断 组件的是显示和隐藏&#xff0c;如何使用for循环来渲染列表等重复的内容。 1.if语法的使用 在小程序中&#xff0c;我们可以使用wx:if"{{条件}}"来判断是否需要渲染该代码块 也可以用 wx:elif 和wx:else 来添加 el…

【java】Spring Boot -- Spring的IOC实现原理

文章目录IOC定义理解IOC不使用IOC&#xff1a;使用IOC&#xff1a;使用IOC的好处IOC提供被依赖对象的方式构造器注入setter 方法注入接口方式注入简单模拟IOC总结IOC定义 IoC 全称为 Inversion of Control&#xff0c;翻译为 “控制反转”&#xff0c;它还有一个别名为 DI&…

【Selenium】十分钟手把手带你学会WebDriver API

目录 1、定位元素【8种】 2、操作测试对象 3、添加等待 4、弹窗类型 5、浏览器的操作 6、键盘事件 7、选择框 8、上传文件 1、定位元素【8种】 元素定位是自动化测试的核心&#xff0c;想要去操作一个对象&#xff0c;第一步就是需要我们先去识别这个对象。每个对象就会…

全民ChatGPT热:快来解锁你的“全能网友”

前 言 2021年11月30日&#xff0c;OpenAI推出人工智能聊天原型ChatGPT&#xff0c;赚足了眼球&#xff0c;在AI界引发了类似AIGC让艺术家失业的大讨论。 据报道&#xff0c;ChatGPT在开放试用的短短几天&#xff0c;就吸引了超过 100 万互联网注册用户。并且社交网络流传出各种…

html5标签

图片&#xff1a;image 主要属性&#xff1a; src&#xff1a;源属性的值是图像的 URL 地址。 alt&#xff1a;用来为图像定义一串预备的可替换的文本。 注意事项&#xff1a; 注意: 假如某个 HTML 文件包含十个图像&#xff0c;那么为了正确显示这个页面&#xff0c;需要加…

速通Spring

尚硅谷2023最新版Spring6课程_bilibili 1 Spring 【强制】Spring是什么&#xff1f; 1) Spring是一款主流的Java EE轻量级开源框架。 轻量级&#xff1a;体积很小&#xff0c;且不需要依赖于其他组件。 2) 狭义的Spring。 Spring Framework。 3) 广义的Spring。 以Spring F…

python对多个csv文件进行合并(表头需一致)

之前写过python对【多个Excel文件】中的【单个sheet】进行合并&#xff0c;参考&#xff1a;点我 之前也写过python对【多个Excel文件】中的【多个sheet】进行合并&#xff0c;参考&#xff1a;点我 今天再写一个python对多个csv格式的文件进行合并的小工具 但是大家切记&am…

GIS开源框架:ArcGIS文件地理数据库(GDB)解析与入库

对于GIS专业毕业的同学&#xff0c;想必对于ArcGIS软件不会太陌生&#xff0c;对于地理数据库也有一定的了解和使用经验。但是&#xff0c;撇开软件操作层面不谈&#xff0c;作为一个WebGIS/GIS开发人员&#xff0c;我们如何通过GIS开源框架去完成地理数据库的自动化解析和入库…

解决不同影像裁剪后栅格数据行列不一致问题

前言在处理栅格数据时&#xff0c;尽管用同一个矢量文件裁剪栅格数据&#xff0c;不同数据来源的栅格行列数也会出现不一致的情况。如果忽略或解决不好&#xff0c;会导致后续数据处理出现意想不到的误差或错误&#xff0c;尤其是利用编程实现数据处理时。因此&#xff0c;应当…

VisualGDB 5.6R9 FOR WINDOWS

Go cross-platform with comfort VisualGDB 是 Visual Studio 的一个非常强大的扩展&#xff0c;它允许您调试或调试嵌入式系统。这个程序有一个非常有吸引力的用户界面&#xff0c;它有许多调试或调试代码的功能。VisualGDB 还有一个向导可以帮助您调试程序&#xff0c;为您提…

【C++】关键字、命名空间、输入和输出、缺省参数、函数重载

C关键字(C98)命名空间产生背景命名空间定义命名空间使用输入&输出缺省参数什么叫缺省参数缺省参数分类函数重载函数重载概念C支持函数重载的原理--名字修饰C关键字(C98) C总计63个关键字&#xff0c;C语言32个关键字。 下面我们先看一下C有多少关键字&#xff0c;不对关键…

Linux 解压JAR包 查看class内容

快速解决方案 查询class相对路径&#xff1a;jar tf test.jar | grep "test.class"单独解压class文件&#xff1a;jar xvf test.jar com/test/test.class查看class文件内容&#xff1a;javap -c com/test/test.class 背景 服务运行后&#xff0c;日志打印出来发现…

【taichi】利用 taichi 编写深度学习算子 —— 以提取右上三角阵为例

本文以取 (bs, n, n) 张量的右上三角阵并展平为向量 (bs, n*(n1)//2)) 为例&#xff0c;展示如何用 taichi 编写深度学习算子。 如图&#xff0c;要把形状为 (bs,n,n)(bs,n,n)(bs,n,n) 的张量&#xff0c;转化为 (bs,n(n1)2)(bs,\frac{n(n1)}{2})(bs,2n(n1)​) 的向量。我们先写…

各种素材网站大全【全部倾倒,福利倒计时-JS,HTML,游戏素材,UI,图片素材等

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;解忧杂货铺 ⭐各种素材网站大全⭐ 文章目录⭐各种素材网站大全⭐&#x1f3b6;大家必逛的四大天王…

STM32F769BIT6微控制器STM32F769IGT6详细规格

说明STM32F7 32 位 MCUFPU 基于高性能的 ARMCortex-M7 32 位 RISC 内核&#xff0c;工作频率高达 216MHz。Cortex-M7 内核具有单浮点单元(SFPU)精度&#xff0c;支持所有 ARM 单精度数据处理指令与数据类型。同时执行全套 DSP 指令和存储保护单元&#xff08;MPU&#xff09;&a…

JVM02类加载子系统

1. 加载阶段 通过一个类的全限定名获取定义此类的二进制字节流 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构 在内存中生成一个代表这个类的java.lang.Class对象&#xff0c;作为方法区这个类的各种数据的访问入口 加载class文件的方式 从本地系统中直接…

六、HTTP 首部字段

HTTP 首部字段 一、HTTP 报文首部 HTTP 请求报文由方法、URI、HTTP 版本、HTTP 首部字段等部分构成。 HTTP 响应报文由HTTP版本、状态码&#xff08;数字和原因短语&#xff09;、HTTP首部字段3部分构成。 HTTP 协议的请求和响应报文中必定包含 HTTP 首部。首部内容为客户端…

TensorRT如何工作

TensorRT如何工作 本章提供了有关 TensorRT 工作原理的更多详细信息。 文章目录TensorRT如何工作5.1. Object Lifetimes5.2. Error Handling and Logging5.3 Memory5.3.1. The Build Phase5.3.2. The Runtime Phase5.4. Threading5.5. Determinism5.1. Object Lifetimes Tenso…

ChatGPT通俗导论:从RL之PPO算法、RLHF到GPT-N、instructGPT

前言 自从我那篇BERT通俗笔记一经发布&#xff0c;然后就不断改、不断找人寻求反馈、不断改&#xff0c;其中一位朋友倪老师(之前我司NLP高级班学员现课程助教老师之一)在谬赞BERT笔记无懈可击的同时&#xff0c;给我建议到&#xff0c;“后面估计可以尝试尝试在BERT的基础上…

MACD多周期共振指标公式,日周月共振

有人问多周期MACD怎么写&#xff0c;编写指标的难度其实不大&#xff0c;主要问题是解决多周期MACD显示的问题。日线、周线、月线三个周期&#xff0c;每个周期都有快线DIF和慢线DEA两条线&#xff0c;一共6条&#xff0c;怎么在副图上清晰显示出来。 一、MACD多周期共振指标公…