Java优先级队列(堆)

🐵本篇文章将对优先级队列(堆)的相关知识进行讲解


一、优先级队列

队列是一种“先入先出”的数据结构,但有时操作的数据带有优先级,需要优先处理,这时普通的队列就不能满足需求。比如:在排队取票时,往往会有老人优先、退伍军人优先等。这就可以理解为优先级队列的一种体现

优先级队列的底层是由一种叫的数据结构实现的

二、堆

2.1 什么是堆

将元素按照完全二叉树的顺序存储方式存储到一维数组中,堆分为大根堆小根堆

如上图,如果满足每一个父亲节点的值都大于其左右孩子结点的值,则称为大根堆

如上图,如果满足每一个父亲节点的值都小于其左右孩子结点的值,则称为小根堆

2.2 堆的创建

堆的向下调整

public class Heap {
    public int[] elem;
    public int usedSize; //记录数组中元素的个数

    public Heap() {
        this.elem = new int[10]; //构造方法:数组的初始容量为10
    }

    public void initElem(int[] array) { //初始化elem数组
        for (int i = 0; i < elem.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
    }
}

以大根堆为例,如果一棵完全二叉树是大根堆,则其每一课子树也都是大根堆,定义一个parent表示最后一棵子树的根结点的下标,再定义一个child表示parent结点的左孩子的下标

每调整完一个子树后就让parent--,去调整下一棵子树

那么该如何用代码表示parent和child的下标:首先是parent,usedSize表示数组中元素的个数,usedSize - 1就是最后一个结点的下标,根据二叉树的性质,让其减1再除2就是要得到的最后一个子树的根结点下标,表示为(usedSize -1 - 1)/ 2,child同样也是根据二叉树的性质,child = 2 * parent + 1

得到parent和child后,开始对第一个子树进行调整:首先要让child表示为左右孩子较大值结点的下标

    public void creatBigHeap() { //创建大根堆
        for (int parent = (usedSize - 1 - 1) / 2; parent >= 0 ; parent--) {
            siftDown(parent, usedSize);
        }
    }
    public void siftDown(int parent, int end) { //向下调整
        int child = 2 * parent + 1;
        if (child + 1 < usedSize && elem[child] < elem[child + 1]) {
            child++;
        }
        ...
    }

接下来就是比较elem[parent]elem[child],如果孩子结点大于其父亲结点则进行交换,交换完后,让parent = child,child = 2 * parent + 1,继续向下调整

如上图,此时说明这个子树已经调整完毕,当在调整{19, 34}这个树时当parent = 5、child = 11时,这个树就调整完了,所以可以用代码写一个循环,循环条件为child < usedSize

    public void siftDown(int parent, int end) {
        int child = 2 * parent + 1;
        while(child < end) {
            if (child + 1 < usedSize && elem[child] < elem[child + 1]) {
                child++;
            }
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }

        }
    }
    private void swap(int child, int parent) {
        int tmp = elem[child];
        elem[child] = elem[parent];
        elem[parent] = tmp;
    }

那么整个大根堆就创建完了,完整代码如下:

public class Heap {
    public int[] elem;
    public int usedSize; //记录数组中元素的个数

    public Heap() {
        this.elem = new int[10]; //构造方法:数组的初始容量为10
    }
    public void initElem(int[] array) { //初始化elem数组
        for (int i = 0; i < elem.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
    }

    public void creatBigHeap() { //时间复杂度为O(n)
        for (int parent = (usedSize - 1 - 1) / 2; parent >= 0 ; parent--) {
            siftDown(parent, usedSize);
        }
    }
    public void siftDown(int parent, int end) {
        int child = 2 * parent + 1;
        while(child < end) {
            if (child + 1 < usedSize && elem[child] < elem[child + 1]) {
                child++;
            }
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }

        }
    }
    private void swap(int child, int parent) {
        int tmp = elem[child];
        elem[child] = elem[parent];
        elem[parent] = tmp;
    }
}

堆的向上调整和向下调整类似

    public void siftUp(int child) {
        int parent = (child - 1) / 2;
        while(parent > 0) {
            if (child + 1 < usedSize && elem[child] < elem[child + 1]) {
                child++;
            }
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                child = parent;
                parent = (child - 1) / 2;
            } else {
                break;
            }
        }
    }

2.3 堆的插入与删除

堆的插入可以将元素放到数组的末尾,然后可以通过向上调整将其调整为大根堆或小根堆

    public void offer(int key) {
        if (isFull()) { //判断是否需要扩容
            elem = Arrays.copyOf(elem, 2 * usedSize);
        }
        
        elem[usedSize] = key;
        usedSize++;
        
        siftUp(usedSize - 1);
    }

堆的删除,由于堆顶的元素,所以可以将堆顶的元素和最后一个元素交换,再让usedSize--后将其向下调整为大根堆或小根堆即可

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

2.4 堆排序

堆排序就是按照堆的思想排序,假如要将一组数据按照从小到大排序,则先将这组数据创建为大根堆,不建小根堆是因为其不能保证左右孩子也是有序的。以下图为例:

由于是大根堆,所以堆顶元素是最大的,将堆顶元素和最后一个元素交换,交换后再调整为大根堆,此时再将堆顶元素与倒数第二个元素交换,交换后再调整为大根堆,如此循环

    public void heapSort() {
        int end = usedSize - 1; //此时为最后一个元素的下标
        while(end > 0) {
            swap(0, end);
            siftDown(0, end - 1);//由于交换了0下标,所以只需要从0下标开始向下调整
                                //交换完后end位置不再属于大根堆,所以在end -1处结束调整
            end--;
        }
    }

三、PriorityQueue接口介绍

该接口的底层是一个小根堆,先来介绍在该接口中的几个字段

3.1 构造方法

首先是上述三个基础的构造方法,可以观察到这三个构造方法都调用了另外一个构造方法:

调用不带参数的构造方法,此时数组的大小为初始容量:11

调用第一个带参数的构造方法,此时数组的大小为initalCapacity

第三个构造方法,其参数是一个比较器,接下来简单复习一下比较器(Comparator接口)

class Person {
    public int age;
    public String name;
    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }
}

class Imp implements Comparator<Person> {
    public int compare(Person p1, Person p2) {
        return p1.name.compareTo(p2.name); //调用了String类的compareTo方法
            //前者>后者,返回一个大于0的数
            //前者<后者,返回一个小于0的数
            //前者=后者,返回0
    }
}

class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Sans", 20);
        Person p2 = new Person("Frisk", 8);
        Imp imp = new Imp();
        System.out.println(imp.compare(p1, p2));
    }
}

调用第二个带参数的构造方法

那么传比较器作为参数究竟有什么用,接下来通过分析offer方法的源码进行讲解

3.2 接口中的常用方法

boolean offer(E e);

插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度为O(log₂N),如果空间不够就会进行扩容

E peek();

获取优先级最高的元素,即堆顶的元素,如果优先级队列为空,则返回null

E poll();

删除优先级最高的元素并返回,如果优先级队列为空,则返回null,时间复杂度为O(log₂N)

int size();

获取优先级队列中有效元素的个数

void clear();

清空优先级队列

boolean isEmpty();

判断优先级队列是否为空,如果为空,则返回true


🙉以上就是本文关于优先级队列的全部内容,接下来会对排序的相关知识进行讲解

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《JAVA与模式》之抽象工厂模式

系列文章目录 文章目录 系列文章目录前言一、使用简单工厂模式的解决方案二、引进抽象工厂模式三、抽象工厂模式结构四、抽象工厂模式的优缺点前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看…

Github上哪些好用的安全工具1

专注于web漏洞挖掘、内网渗透、免杀和代码审计&#xff0c;感谢各位师傅的关注&#xff01;网安之路漫长&#xff0c;与君共勉&#xff01; URLFinder 一款快速提取网页信息的工具。该项目可以快速爬取网页上的 URL 地址、JS 文件里的 API 接口等信息&#xff0c;支持批量抓取…

推荐算法中经典排序算法GBDT+LR

文章目录 逻辑回归模型逻辑回归对于特征处理的优势逻辑回归处理特征的步骤 GBDT算法GBDTLR算法GBDT LR简单代码实现 逻辑回归模型 逻辑回归&#xff08;LR,Logistic Regression&#xff09;是一种传统机器学习分类模型&#xff0c;也是一种比较重要的非线性回归模型&#xff0…

LeetCode199题:二叉树的右视图(python3)

代码思路&#xff1a;深度优先搜索&#xff0c;每次总访问右子树&#xff0c;value_depth用dict存放&#xff0c;深度为索引&#xff0c;存放节点的值&#xff0c;stack从根节点[(root, 0)]开始&#xff0c;添加node和depth class Solution:def rightSideView(self, root: Opt…

深入理解 CSS——CSS进阶与实践(5w字高频面试题整理)

本文总结了CSS高频面试题&#xff0c;并搭配了演示动画进行CSS样式演示。介绍了关于如何理解盒模型&#xff0c;如何实现块级元素水平居中&#xff0c;如何实现两侧固定中间自适应的三栏布局、如何实现两栏布局&#xff0c;如何进行响应式设计&#xff0c;对BFC的理解&#xff…

04- 基于SpringAMQP封装RabbitMQ,消息队列的Work模型和发布订阅模型

SpringAMQP 概述 使用RabbitMQ原生API在代码中设置连接MQ的参数比较繁琐,我们更希望把连接参数写在yml文件中来简化开发 SpringAMQP是基于AMQP协议定义的一套API规范,将RabbitMQ封装成一套模板用来发送和接收消息 AMQP(Advanced Message Queuing Portocol)是用于在应用程序…

【危化品泄漏源定位】基于改进哈里斯鹰优化算法的危化品泄漏源定位算法 溯源定位算法【Matlab代码#63】

文章目录 【获取资源请见文章第7节&#xff1a;资源获取】1. 算法概述2. 原始哈里斯鹰算法&#xff08;HHO&#xff09;3. 改进哈里斯鹰算法&#xff08;IHHO&#xff09;3.1 动态自适应逃逸能量3.2 动态扰动策略 4. 构建源强和位置反算模型5. 部分代码展示6. 仿真结果展示7. 资…

牛-迈面试题----答案/类似题/知识点

来源在这里 来源在这里 1.Redis的优势 (1) 速度快&#xff0c;因为数据存在内存中&#xff0c;类似于HashMap&#xff0c;HashMap的优势就是查找和操作的时间复杂度都很低 (2)支持丰富数据类型&#xff0c;支持string&#xff0c;list&#xff0c;set&#xff0c;sorted set&…

Linux之线程互斥

目录 一、问题引入 二、线程互斥 1、相关概念 2、加锁保护 1、静态分配 2、动态分配 3、锁的原理 4、死锁 三、可重入与线程安全 1、概念 2、常见的线程不安全的情况 3、常见的线程安全的情况 4、常见不可重入的情况 5、常见可重入的情况 6、可重入与线程安全联系…

idea 导入项目

idea 导入项目并运行 导入设置设置 jdk查看maven 设置 导入 在项目首页 或者 file 选择 open, 然后选择项目根路径 设置 设置 jdk 查看maven 设置

Linux基础命令[18]-whoami

文章目录 1. whoami 命令说明2. whoami 命令语法3. whoami 命令示例4. 总结 1. whoami 命令说明 whoami&#xff1a;用于显示当前用户名&#xff0c;功能与 id -un 相同。基本信息如下&#xff1a; Usage: whoami [OPTION]... Print the user name associated with the curre…

数码管动态扫描显示

摸鱼记录 Day_16 (&#xff9f;O&#xff9f;) review 前边已经学习了&#xff1a; 串口接收&#xff1a;Vivado 串口接收优化-CSDN博客 1. 今日摸鱼任务 串口接收数据 并用数码管显示 (&#xff9f;O&#xff9f;) 小梅哥视频&#xff1a; 17A 数码管段码显示与动态扫…

ES6(一):let和const、模板字符串、函数默认值、剩余参数、扩展运算符、箭头函数

一、let和const声明变量 1.let没有变量提升&#xff0c;把let放下面打印不出来&#xff0c;放上面可以 <script>console.log(a);let a1;</script> 2.let是一个块级作用域,花括号里面声明的变量外面找不到 <script>console.log(b);if(true){let b1;}//und…

matlab 基操~

MATLAB基本操作 1. 对象定义 使用sym定义单个对象、使用syms定义多个对象 2. 使用limit求极限 $$ \lim_{v \rightarrow a} f(x) $$ limit(f,v,a) % 使用limit(f,v,a,left)可求左极限 3. 导数 使用diff(f,v,n)对$ f(v)v^{t-1} $求 $ n $ 阶导 $ \frac{d^nf}{d^nv} $&#xf…

【蓝桥杯-单片机】基础模块:数码管

文章目录 【蓝桥杯-单片机】基础模块&#xff1a;数码管01 数码管原理图什么是位选和段选共阳极数码管和共阴极数码管的区分&#xff08;1&#xff09;共阳极数码管&#xff08;Common Anode&#xff09;&#xff1a;&#xff08;2&#xff09;共阴极数码管&#xff08;Common …

C语言中大小写字母如何转化

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

【Miniconda】基于conda列出当前环境下所有已创建的虚拟环境

【Miniconda】基于conda列出当前环境下所有已创建的虚拟环境 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您的…

MS08-067 漏洞利用与安全加固

文章目录 环境说明1 MS08_067 简介2 MS08_067 复现过程3 MS08_067 安全加固 环境说明 渗透机操作系统&#xff1a;2024.1漏洞复现操作系统: Windows XP Professional with Service Pack 2- VL (English)安全加固复现操作系统&#xff1a;Windows XP Professional with Service …

Windows系统搭建Cloudreve结合内网穿透打造可公网访问的私有云盘

目录 ⛳️推荐 1、前言 2、本地网站搭建 2.1 环境使用 2.2 支持组件选择 2.3 网页安装 2.4 测试和使用 2.5 问题解决 3、本地网页发布 3.1 cpolar云端设置 3.2 cpolar本地设置 4、公网访问测试 5、结语 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&#xff…

BUGKU-WEB shell

题目描述 题目截图如下&#xff1a; 描述&#xff1a; $poc "a#s#s#e#r#t";$poc_1 explode("#", $poc);$poc_2 $poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5];$poc_2($_GET[s])进入场景看看&#xff1a;是一个空白的界面 解题思路 …