Java-数据结构-优先级队列(堆)-(一) (;´д`)ゞ

news2024/12/25 12:13:59

文本目录:

❄️一、优先级队列:

          ➷ 1、概念:

❄️二、优先级队列的模拟实现:

          ➷ 1、堆的概念:

          ➷ 2、堆的性质:

           ➷ 3、堆的创建:

  ▶ 向下调整:

            ➷ 4、堆的插入和删除:

       ▶ 堆的插入:

 ☞ 思路:

 ☞ 代码:

        ▶ 堆的删除:

 ☞ 思路:

 ☞ 代码:

            ➷ 5、堆的判空和判满:

            ➷ 6、堆的对顶数据:

             ➷ 7、堆的总代码:

❄️总结:


❄️一、优先级队列:

          1、概念:

     我们在前面是介绍过队列的,队列是一种先进先出的数据结构,但是在有些情况下呢,我们操作的数据有优先级,这时候,在我们出队列的时候呢,可能需要先出优先级高的先出队列,那么在这种情况下呢,我们的队列就是有些不合适了,比如呢,我们在 玩游戏的时候呢,来个电话,是不是手机先处理电话,之后再处理游戏去。

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


❄️二、优先级队列的模拟实现:

     在 JDK1.8 中呢,我们的 Priority Queue 的底层使用了堆的数据结构,而对于 堆呢就是在 完全二叉树 的基础上进行一些调整的。

          1、堆的概念:

   如果有一个关键码的集合 K = {K0,K1,K2,.......,Kn - 1},把它的所有数据都按照 完全二叉树的顺序存储方式存储 在一个一维数组中,并且满足:

     Ki <= K2i + 1 且 Ki <= K2i + 2 (Ki >= K2i + 1 且 Ki >= K2i + 2) i = 0,1,2,3,4,5.......,

   则称为 小堆(大堆)。

   在这里我们 将根节点最大的堆称为 最大堆 或者 大根堆

                      将根节点最小的堆称为 最小堆 或者 小根堆


           2、堆的性质:

•  堆中某个节点的值总是 不大于  或者 不小于 其父节点的值。

•  堆是一颗完全二叉树。

我们来看看 大根堆 和 小根堆 的逻辑结构和存储结构:

     我们的存储方式是采用 层序存储 的 规则来进行存储到数组中。这样就非常的高效了

     我们要注意如果不是 完全二叉树 的情况下呢,我们的不能使用 层序遍历的存储规则,这样会浪费空间,所以如果不是完全二叉树不能这样做。


            3、堆的创建:

     我们在创建堆之前呢,我们先来做一些准备工作,我们要注意我们的堆的底层使用的是数组进行实现的。

 我们将这些数据变成 大根堆 或者 小根堆 但是呢,我们要如何才能创建 大根堆 或者 小根堆 呢?

在创建 大根堆 或者 小根堆 之前呢,我们先把这个数组的值呢变成 完全二叉树 来看看:

我们红色的字体就是我们的数组的下标。这个就是我们这组数据的 完全二叉树 的逻辑结构。

 那么我们要如何创建堆呢?这里呢我们要了解到一个知识点——向下调整


  ▶ 向下调整:

      我们这里是 从最后一颗子树进行调整,找到最后一颗子树的根节点,再比较这个根节点的左右节点谁大,我们把根节点和这个最大值进行调整,这样循环调整每一颗子树。这样就可以调整成大根堆。

这里呢,我们直接看图来理解这个问题:

这就是我们的大根堆的调整方法从 完全二叉树 调整。 

这里我们需要一些必须要求的东西:

1、首先要找到最后一颗子树的位置,也就是最后一颗子树的根节点。

     这里我们使用一个公式:(数组的长度 - 1 - 1)/ 2  就是我们的最后一颗子树的根节点。我们使用 parent 标记这个节点。

2、我们如何找到我们的下一颗子树的根节点位置?

      这个就比较简单了,我们直接 parent-- 就可以了。直至我们的 parent 下标 >= 0 即可比如:

3、我们要知道我们的子树的 左子树 ,为什么是左子树呢?因为是由 完全二叉树 调整而来,如果        没有左子树就没有右子树,所以只需要找到左子树即可

      那么怎样去找呢?这里也有公式:child = parent * 2 + 1 ,就是我们左子树。但是这个呢我们还要找其左右子树最大的值,不是直接对child进行交换的并且要注意我们的 child 要 小于 有效的数组长度

 4、我们怎样知道我们的调整结束了?

      这里我们不能调整一次就结束,这里的结束条件就是,当我们的 左子树这个节点的下标比有效数组长度长的时候就是结束的时候了

      每次我们要对 child 和 parent 进行调整。parent = child     child = parent * 2 + 1


我们来演示一变看看如何进行的:

这就是我们的 向下调整  成 大根堆 的操作。来看看这个 向下调整 的代码:

/**
     *
     * @param parent 每颗子树调整时候的起始位置
     * @param usedSize 用来判断 每颗子树什么时候结束的
     */
    private void shiftDown(int parent,int usedSize) {
        int child = parent * 2 + 1;//parent根节点的左子树的节点

        while (child < usedSize) {

            //判断有没有右子树,如果有就把 child 设置为最大的值的下标
            if (child + 1 < usedSize && elem[child + 1] > elem[child]) {
                //右子树比左子树大把 child + 1,就是右子树
                child += 1;
            }

            if (elem[parent] < elem[child]) {
                //进行交换
                swap(elem,child,parent);
                //调整完,把 parent 和 child 进行调整位置
                parent = child;
                child = parent * 2 + 1;
            }else {
                //这是 parent下标的值大于child,跳出
                break;
            }
        }
    }

    private void swap(int[] elem,int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }

     这个呢就是我们的 向下调整 的 创建大根堆,对于我们的要是 创建 小根堆 的话就是把其小的数值进行调整就可以了,就是反过来进行调整


     我们这个时候来看看对于堆的创建,了解到 向下调整 之后呢,就是非常的容易了,我们呢就是把每一个 parent 进行 向下调整,这里就用到循环遍历就可以了。

     我们来看代码:

//堆的创建
    public void createHeap() {
        for (int parent = (this.usedSize - 1 - 1) / 2; parent >= 0 ; parent--) {
            shiftDown(parent,this.usedSize);
        }
    }

Ok啊,这样我们的 大根堆的创建 就创建完成了。


             4、堆的插入和删除:

       ▶ 堆的插入:

 ☞ 思路:

      对于我们的堆的插入的操做呢,我们的插入一定是在最后一个位置插入的,这个位置,我们一定是知道的,就是我们的 usedSize 这个下标的位置插入数据。我们就可以根据这个位置求出其插入的值的根节点的位置。

       我们来看图来理解:

    这个 90 就是我们新插入的值,我们再对其进行 向上调整,我们是知道插入值的下标,就可以计算到根节点的下标,就可以进行 向上调整了 。 

再对其进行向上调整就可以了。 

 

这里我们也要注意当我们的数组使用满了后,我们就需要扩容了。

这个就是插入的操作了。我们来看看代码是如何编写的: 

 ☞ 代码:
//堆的插入
    public void offer(int val) {

        if (usedSize == elem.length) {
            //满了。2倍扩容
            this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
        }

        //插入操作
        this.elem[usedSize] = val;
        //这个时候 usedSize 位置就是我们的子树的坐标位置
        shiftUp(usedSize);
        this.usedSize++;
    }

    private void shiftUp(int child) {

        int parent = (child - 1) / 2;

        while (parent >= 0) {
            //进行向上调整
            if (this.elem[parent] < this.elem[child]) {
                //交换
                swap(elem,child,parent);
                //parent 和 child 调整位置
                child = parent;
                parent = (child - 1) / 2;
            }
            else {
                break;
            }
        }
    }

OK,这个呢就是我们的插入操作的代码了之后我们来看看删除操作是如何做到的。 


        ▶ 堆的删除:

 ☞ 思路:

    这个删除操作还是很简单的,我们需要删除优先级高的数据,所以我们删除的就是 0 下标的值

我们呢有三个步骤:

1、把堆的 0 下标的数据 和 usedSize - 1 这个下标的数据进行交换,就是第一个数据和最后一个         数据进行交换。

2、我们把有效数组长度减一。

3、我们就是 0 这个下标不是大根堆的结构,所以我们对 0 这个下标进行 向下调整 操作。

这个呢就是我们删除的思路了,我们接下来看看其代码: 

 ☞ 代码:
//堆的删除操作
    public int poll() {
        //删除优先级最高的就是0下标的值,有三个步骤:
        //1、把堆的第一个数据和最后一个数据进行交换
        //2、把有效数据长度进行减1
        //3、把 0 下标的值进行向下调整
        if (usedSize == 0) {
            //堆为空,结束
            return -1;
        }

        int val = elem[0];
        //1、
        swap(elem,0,usedSize - 1);
        //2、
        usedSize--;
        //3、
        shiftDown(0,usedSize);

        return val;
    }

             5、堆的判空和判满:

这两个操作就非常的简单了,我们直接来看代码:

    //判空
    public boolean isEmpty() {
        return usedSize == 0;
    }

    //判满
    public boolean isFull() {
        return usedSize == this.elem.length;
    }

             6、堆的对顶数据:

这个同样非常的简单,我们直接返回下标为 0 的值就可以了。

    //返回堆顶的数据
    public int peek() {
        if (isEmpty()) {
            return -1;
        }
        
        return this.elem[0];
    }

这样呢我们所有的基本操作就都完成了,我们来看一下总代码: 


              7、堆的总代码:

import java.util.Arrays;

public class TestHeap {
    public int[] elem;
    public int usedSize;

    public TestHeap() {
        //初始话
        this.elem = new int[10];
    }

    public void initElem(int[] array) {
        //把elem里的值设置为我们输入的值
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
        }
    }

    

    /**
     * 建堆的时间复杂度为O(N)。
     * 大根堆
     * @param parent 每颗子树调整时候的起始位置
     * @param usedSize 用来判断 每颗子树什么时候结束的
     */
    private void shiftDown(int parent,int usedSize) {
        int child = parent * 2 + 1;//parent根节点的左子树的节点

        while (child < usedSize) {

            //判断有没有右子树,如果有就把 child 设置为最大的值的下标
            if (child + 1 < usedSize && elem[child + 1] > elem[child]) {
                //右子树比左子树大把 child + 1,就是右子树
                child += 1;
            }

            if (elem[parent] < elem[child]) {
                //进行交换
                swap(elem,child,parent);
                //调整完,把 parent 和 child 进行调整位置
                parent = child;
                child = parent * 2 + 1;
            }else {
                //这是 parent下标的值大于child,跳出
                break;
            }
        }
    }

    private void swap(int[] elem,int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }

    //大根堆的创建
    public void createHeap() {
        for (int parent = (this.usedSize - 1 - 1) / 2; parent >= 0 ; parent--) {
            shiftDown(parent,this.usedSize);
        }
    }

    //堆的插入
    public void offer(int val) {
        if (isFull()) {
            //满了。2倍扩容
            this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
        }

        //插入操作
        this.elem[usedSize] = val;
        //这个时候 usedSize 位置就是我们的子树的坐标位置
        shiftUp(usedSize);
        this.usedSize++;
    }

    private void shiftUp(int child) {
        int parent = (child - 1) / 2;
        while (parent >= 0) {
            //进行向上调整
            if (this.elem[parent] < this.elem[child]) {
                //交换
                swap(elem,child,parent);
                //parent 和 child 调整位置
                child = parent;
                parent = (child - 1) / 2;
            }
            else {
                break;
            }
        }
    }

    //堆的删除操作
    public int poll() {
        //删除优先级最高的就是0下标的值,有三个步骤:
        //1、把堆的第一个数据和最后一个数据进行交换
        //2、把有效数据长度进行减1
        //3、把 0 下标的值进行向下调整
        if (isEmpty()) {
            //堆为空,结束
            return -1;
        }

        int val = elem[0];
        //1、
        swap(elem,0,usedSize - 1);
        //2、
        usedSize--;
        //3、
        shiftDown(0,usedSize);

        return val;
    }

    //返回堆顶的数据
    public int peek() {
        if (isEmpty()) {
            return -1;
        }

        return this.elem[0];
    }

    //判空
    public boolean isEmpty() {
        return usedSize == 0;
    }

    //判满
    public boolean isFull() {
        return usedSize == this.elem.length;
    }
}

❄️总结:

     OK,这篇博客呢就到这里就结束了,这篇博客我们介绍的是 优先级队列 的底层操作代码,下一篇博客我们来看看 Java 自带的 优先级队列吧~~并且还有一些关于我们的优先级队列的习题,敬请期待吧!!!拜拜·~~~

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

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

相关文章

Java 值传递与引用传递

以下是包含引用的完整博客文章&#xff0c;以markdown格式输出&#xff0c;附带“Java 只有值传递”的相关参考来源。 Java 是一种广泛使用的面向对象编程语言&#xff0c;但对于值传递&#xff08;pass by value&#xff09;和引用传递&#xff08;pass by reference&#xff…

只有公网IP地址可以申请SSL证书吗?

是的&#xff0c;只有公网IP地址可以申请SSL证书。这是因为SSL证书主要用于加密互联网上的数据传输&#xff0c;确保通信的安全性和数据的完整性。而公网IP地址是互联网通信的核心&#xff0c;具有全球唯一性&#xff0c;允许互联网上的用户通过它们访问互联网上的资源。 具体…

北森笔试测评之言语理解到底难不难

前篇笔记我提到过&#xff0c;言语理解是最难的&#xff0c;有同学质疑了。言语我都会做呀&#xff0c;甚至有的可以搜到&#xff0c;而图标和图形我有的不会。这里需要指出&#xff0c;会做不等于作对&#xff0c;可以回顾到高中甚至初中的时候&#xff0c;感觉做的好的一般都…

dcmtk的自动输入数据纠错模式对DICOMDIR读取的影响

软件版本 dcmtk 3.6.7 自动纠错的全局变量 输入数据的自动纠错是一个全局变量&#xff0c;定义在dcmtk/dcmdata/dcobject.h中&#xff0c;如下所示&#xff1a; /** This flags defines whether automatic correction should be applied to input* data (e.g.\ stripping …

【多视图学习】基于多视图信息瓶颈的鲁棒表示学习

论文链接 代码链接 0.论文摘要和信息 摘要 信息瓶颈原理为表示学习提供了一种信息论方法&#xff0c;通过训练编码器来保留与预测标签相关的所有信息&#xff0c;同时最小化表示中的其他过量信息的量。然而&#xff0c;原始配方需要标记数据来识别多余的信息。在这项工作中&…

【C++ Primer Plus习题】16.8

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream> #include <set> #includ…

Android 15 正式发布至 AOSP

Google官方宣布&#xff0c;将于近期发布了 Android 15&#xff0c;而在早些时候&#xff0c;Google已经将其源代码推送至 Android 开源项目 (AOSP)。未来几周内&#xff0c;Android 15 将在受支持的 Pixel 设备上正式推出&#xff0c;并将于今年晚些时候在三星、Honor、iQOO、…

easy_cloudantivirus

0x00前言 必须安装在virtualbox 攻击机&#xff1a;kali 靶机 easy_cloudantivirus 链接&#xff1a; https://www.vulnhub.com/entry/boredhackerblog-cloud-av,453/ 0x01信息搜集 经过测试发现靶场IP为192.168.56.106 进一部对IP搜集信息 发现8080端口youhttp服务。…

MySQL系列—12.Undo log

1、概念 DML 操作导致数据变化 , 将变化前的记录写入 Undo 日志。 作用 用于记录更改前的一份 copy &#xff0c;在操作出错时&#xff0c;可以用于回滚、撤销还原&#xff0c;只将数据库 逻辑地恢复到原来的样子 你 插入一条记录时&#xff0c;至少要把这条记录的主键值记下来…

上汽集团社招入职SHL测评:语言理解及数字推理高分攻略、真题题库

上汽集团社招待遇 上汽集团作为国内领先的汽车制造企业&#xff0c;其社招待遇和面试问题一直是求职者关注的焦点。以下是根据最新信息整理的上汽集团社招待遇及面试问题概览&#xff1a; 工资待遇&#xff1a;上汽集团的工资待遇在国内汽车行业中属于较高水平。根据不同职位和…

【C++二叉树】236.二叉树的最近公共祖先

236. 二叉树的最近公共祖先 - 力扣&#xff08;LeetCode&#xff09; 思路分析&#xff1a; 公共祖先满足的特征&#xff1a; p是q的孩子&#xff0c;则q是祖先&#xff0c;反之亦然。p和q是“我”的左子树和右子树中的节点&#xff0c;则“我”是祖先。&#xff08;如题目中给…

物联网——USART协议

接口 串口通信 硬件电路 电平标准 串口参数、时序 USART USART主要框图 TXE: 判断发送寄存器是否为空 RXNE: 判断接收寄存器是否非空 RTS为输出信号&#xff0c;用于表示MCU串口是否准备好接收数据&#xff0c;若输出信号为低电平&#xff0c;则说明MCU串口可以接收数据&#…

springboot实训学习笔记(5)(用户登录接口的主逻辑)

接着上篇博客学习。上篇博客是已经基本完成用户模块的注册接口的开发以及注册时的参数合法性校验。具体往回看了解的链接如下。 springboot实训学习笔记&#xff08;4&#xff09;(Spring Validation参数校验框架、全局异常处理器)-CSDN博客文章浏览阅读576次&#xff0c;点赞7…

NANO 9K玩转RISCV之ARDUINO开发环境

一、前言 在从0-1探索RISCV指令集的路上&#xff0c;我们用百元不到的NANO 9K开发板一步步的实现&#xff1a;  &#xff11;&#xff09;最小的内核架构 &#xff12;&#xff09;取值&#xff0c;译码和执行的过程&#xff08;电路实现ISA指令集&#xff09; &#xff1…

使用神经网络拟合6项参数

使用神经网络拟合6项参数 1. 数据预处理1.1 添加参数解析1.2 数据预处理逻辑1.3 数据归一化及划分1.4 数据标签处理逻辑1.5 数据转torch 2. 定义model2.1 CNN_LSTM2.2 Transformer 3. 定义train脚本3.1 loss和optimizer3.2 train3.3 predict 1. 数据预处理 1.1 添加参数解析 …

Vue+nodejs+express汽车配件商城销售管理系统 i9cgz

目录 技术栈具体实现截图系统设计思路技术可行性nodejs类核心代码部分展示可行性论证研究方法解决的思路Express框架介绍源码获取/联系我 技术栈 该系统将采用B/S结构模式&#xff0c;开发软件有很多种可以用&#xff0c;本次开发用到的软件是vscode&#xff0c;用到的数据库是…

动态分析基础

实验一 Lab03-01.exe文件中发现的恶意代码 问题&#xff1a; 1.找出这个恶意代码的导入函数与字符串列表? 2.这个恶意代码在主机上的感染迹象特征是什么? 3.这个恶意代码是否存在一些有用的网络特征码?如果存在&#xff0c;它们是什么? 解答&#xff1a; 1.找出这个恶意代…

上调铁矿石产量预期后,淡水河谷股价能否重振?

猛兽财经的核心观点&#xff1a; &#xff08;1&#xff09;尽管市场面临挑战&#xff0c;但淡水河谷(VALE)还是上调了2024年的铁矿石产量预期。 &#xff08;2&#xff09;第二季度业绩喜忧参半;收入减少&#xff0c;但铁矿石出货量却很强劲。 &#xff08;3&#xff09;投资者…

【渗透测试】-vulnhub源码框架漏洞-Os-hackNos-1

vulnhub源码框架漏洞中的CVE-2018-7600-Drupal 7.57 文章目录  前言 1.靶场搭建&#xff1a; 2.信息搜集&#xff1a; 主机探测&#xff1a; 端口扫描&#xff1a; 目录扫描&#xff1a; 3.分析&#xff1a; 4.步骤&#xff1a; 1.下载CVE-2018-7600的exp 2.执行exp: 3.写入木…

QCustomPlot笔记(一)

文章目录 简介将帮助文档添加到Qt Creator中编译共享库cmake工程编译提示ui_mainwindow.h找不到qcustomplot.h文件 环境:windowsQt Creator 10.0.1cmake 简介 QT中用于绘制曲线的第三方工具 下载地址&#xff1a;https://www.qcustomplot.com/index.php/download 第一个压缩…