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

🎉欢迎大家观看AUGENSTERN_dc的文章(o゜▽゜)o☆✨✨

🎉感谢各位读者在百忙之中抽出时间来垂阅我的文章,我会尽我所能向的大家分享我的知识和经验📖

🎉希望我们在一篇篇的文章中能够共同进步!!!

🌈个人主页:AUGENSTERN_dc

🔥个人专栏:C语言 | Java | 数据结构

⭐个人格言:

一重山有一重山的错落,我有我的平仄

一笔锋有一笔锋的着墨,我有我的舍得

目录

1.概念:

2. 堆的分类:

2.1 大根堆:

2.2 小根堆:

3. 堆的创建:

3.1 堆类的构建:

3.2 双亲节点和孩子节点的关系:

3.3 堆的初创建:

3.4 向下调整:

3.5 向上调整:

4. 优先级队列的模拟实现:

5. 优先级队列的模拟实现整体源码:


1.概念:

在我们之前的队列的文章中介绍过,队列是一种先进先出的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。

如果我们给每个元素都分配一个数字来标记其优先级,不妨设较小的数字具有较高的优先级,(在后续中我们会讲到,这是小根堆)这样我们就可以在一个集合中访问优先级最高的元素并对其进行查找和删除操作了。这样,我们就引入了优先级队列这种数据结构。 优先级队列(priority queue) 是0个或多个元素的集合,每个元素都有一个优先权,对优先级队列执行的操作有

(1)查找

(2)插入一个新元素

(3)删除

一般情况下,查找操作用来搜索优先权最大的元素,删除操作用来删除该元素 。对于优先权相同的元素,可按先进先出次序处理或按任意优先权进行。

在jdk1.8中,PriorityQueue的底层是用堆这一数据结构实现的;

2. 堆的分类:

堆在逻辑上是一颗完全二叉树,但是堆的实现却是由数组实现的,我们是将这颗完全二叉树按照层序遍历的方式存放在数组中的;

堆分为两种:

2.1 大根堆:

大根堆是指根节点的值最大左右子节点的值都小于根节点的完全二叉树按照层序遍历的方式存放到数组中的一个堆结构;

要想真正的了解堆这个结构,我们不妨从绘图开始理解:

首先我们试着画一个完全二叉树:

将上图的完全二叉树按照层序遍历的方式存放在数组中,如上图,就是一个大根堆;

我们会发现,在上图中的完全二叉树中,根节点25 的值是最大的,根节点25的左右节点的值都比25要小,同时,我们会发现 ,20节点和17节点的左右节点的值同样小于根节点的值;

这就是大根堆的特性;

2.2 小根堆:

小根堆和大根堆则相反,根节点的值要小于左右节点的值;

如下图

3. 堆的创建:

(!!!在接下来的内容中,所有的堆都以小根堆的形式创建!!!)

3.1 堆类的构建:

public class my_PriorityQueue {
    public int[] elem;              //堆的底层是数组实现的;
    public int usedSize;            //数组的使用长度

    private static final int DEFAULT_INITIAL_CAPACITY = 11;     //数组的默认长度

    public my_PriorityQueue() {
        //不带参数的构造方法
    }
}

3.2 双亲节点和孩子节点的关系:

如果堆的存储结构也是一颗完全二叉树的话,想要从根节点找到左右子树是很简单的事情,但是堆的存储结构是一个数组,那么我们又要如何才能找到他的左右子树呢?

以上图的小根堆为例:

由于堆的底层是由数组实现的,那么每一个节点都会有一个对应的下标,我们将其标明在图上;

下标为0的节点的左右子树为1  和  2;下标为1的节点的左右子树的节点的下标为3 和 4;

假设双亲节点为  parent, 孩子节点为 child, 我们不难发现parent 和 child的关系:

(child - 1)/ 2 = parent

这就是双亲节点和孩子节点的关系;

有了这个关系,我们就可以开始试着创建堆了;

3.3 堆的初创建:

假如我们有一个空的小根堆,我们开始向空堆中插入元素,我们先插入值为4 的元素;

接下来为了保持小根堆这个结构,在插入元素之后,我们就需要开始考虑了;

首先我们将元素直接插在4的后面;

如果我们插入的值比插入节点的双亲节点(也就是4节点)大,我们应该保持插入元素的位置不变,

但是如果我们插入的元素比4小呢?  我们就应该将该节点和4节点交换位置;

如图:

那是不是,每次我们插入元素的时候,我们需要进行比较和判断;

看插入的元素的大小和其双亲节点的大小相较之下如何;

但是,随着元素的增多:

如果我们插入一个值为2的节点,我们发现,我们不仅需要2和15进行狡猾,并且交换玩之后,我们需要将2和5再次进行交换,这就会影响整棵完全二叉树;

同时我们会发现,我们有两种调整的方法,我们称为向下调整向上调整

在创建堆的时候我们一般使用向下调整:

我们用createHeap表示创建小根堆方法,  shiftDown表示向下调整方法;

public void createHeap() {
        elem = new int[DEFAULT_INITIAL_CAPACITY];       //构造一个空的,大小为默认大小的堆
    }

    public void createHeap(int[] array) {               //构造一个元素和array数组相同的array小根堆
        elem = array.clone();
        usedSize = array.length;
        int child = array.length - 1;                   //孩子节点的下标
        int parent = (child - 1) / 2;                   //双亲节点的下标
        while (parent >= 0) {                           //从最后一个孩子节点的双亲节点一直向下调整到下标为0的节点
            shiftDown(parent, usedSize - 1);        //向下调整
            parent--;                           
        }
    }

3.4 向下调整:

以上图为例:向下调整就是从最后一个元素的双亲节点开始,依次和子节点比较大小,若需要互换,则进行调整,直到双亲节点的下标为0为止;

如图,就是依次将值为5的节点和值为22的节点, 值为15的节点中的最大值比较,若需要交换则进行调整,一直从值为5的节点调整到值为2的节点为止;

向下调整一般在创建堆的时候进行使用:

接下来我们开始对小根堆的创建:

首先我们先随意给定一个整形数组,将其按照完全二叉树的逻辑排列

最后一个节点下标为5;那么其双亲节点为 ( 5 - 1)/ 2 = 2;

随后我们需要判断下标为2的节点和下标为5的节点的大小,一直从下标为2的节点判断到下标为0的节点为止;

代码的思路大概构建出来了,我们开始着手写向下调整的代码:

private void swap(int x, int y) {
        int tmp = elem[x];
        elem[x] = elem[y];
        elem[y] = tmp;
    }
private void shiftDown(int root,int len) {
        int parent = root;
        int child = parent * 2 + 1;
        while (child <= len) {                                              //若有两个孩子节点
            child = elem[child] < elem[child + 1] ? child : child + 1;      //找出两个孩子节点中的最大的节点
            if (elem[parent] > elem[child]) {                               //若孩子节点大于双亲节点
                swap(child, parent);                                        //交换孩子节点和双亲节点
                parent = child;                                             //将parent重置为child
                child = parent * 2;                                         //重置child,判断交换后的子树是否需要调整
            } else {
                break;                                                      //若无需调整,则直接跳出循环
            }
        }
    }

3.5 向上调整:

向上调整一般在插入元素的时候使用,例如在已经创建完成的堆中插入一个元素,一般是先将该元素放在数组的最后,然后依次将其和双亲节点进行大小比较,直到孩子节点的下标为0或者不需要和双亲节点进行交换为止,如图所示:

在这样一个小根堆中,我们插入一个元素3

此时的child = 7,parent = 3,首先我们来判断3和17 的大小,很明显,需要交换:

交换完成之后,我们将child = parent = 3,此时的parent = 1;

此时我们继续判断child和parent 的大小关系,还是需要交换3 和 6,再将child = parent,

parent = (child + 1)* 2 = 0;

继续比较child和parent的值的大小关系,发现并不需要比较了,那我们就停止判断即可;

这就是向上调整的思路:

private void shiftUp(int child) {
        int parent = (child - 1) / 2;
        while (parent >= 0 && child != 0) {
            if (elem[child] < elem[parent]) {       
                swap(child, parent);
                child = parent;
                parent = (child - 1) / 2;
            } else  {
                break;
            }
        }
    }

4. 优先级队列的模拟实现:

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

和队列的模拟实现类似,优先级队列同样有插入元素删除元素获得队头元素的方法:

< 1 >  插入元素:

每次插入元素之前,我们需要判断堆是否满了,若满了,则进行扩容:

private void grow() {
        int len = elem.length;
        if (len < 64) {
            elem = Arrays.copyOf(elem, DEFAULT_INITIAL_CAPACITY * 2);
        } else {
            elem = Arrays.copyOf(elem, DEFAULT_INITIAL_CAPACITY + DEFAULT_INITIAL_CAPACITY >> 1);
        }
    }

判断完成后,我们需要将插入元素后的新堆调整为大根堆或者小根堆,我们这里以小根堆为例:

public void offer(int val) {
        if (isFull()) {
            grow();
        }
        if (usedSize == 0) {
            elem[0] = val;
            usedSize++;
            return;
        }
        elem[usedSize] = val;
        shiftUp(usedSize);
        usedSize++;
    }

< 2 >  删除元素:

由于删除元素是将堆顶元素进行删除,我们可以先将堆顶元素和堆末尾的元素进行交换,将堆末尾的元素删除也就是将usedsize - - 即可;

public void pollHeap() {
        swap(0, usedSize - 1);
        usedSize--;
        shiftDown(0, usedSize - 1);
    }

< 3 >  获得队头元素:

public int peekHeap() {
        return elem[0];
    }

还有size()  , isEmpty() ,clear()方法,由于太简单,这里就没有写了;

5. 优先级队列的模拟实现整体源码:

import java.util.Arrays;

public class my_PriorityQueue {
    public int[] elem;              //堆的底层是数组实现的;
    public int usedSize;            //数组的使用长度

    private static final int DEFAULT_INITIAL_CAPACITY = 11;     //数组的默认长度

    public my_PriorityQueue() {
        //不带参数的构造方法
    }

    /**
     * 建堆的时间复杂度:
     *
     * @param array
     */

    public void createHeap() {
        elem = new int[DEFAULT_INITIAL_CAPACITY];       //构造一个空的,大小为默认大小的堆
    }

    public void createHeap(int[] array) {               //构造一个元素和array数组相同的array小根堆
        elem = array.clone();
        usedSize = array.length;
        int child = array.length - 1;                   //孩子节点的下标
        int parent = (child - 1) / 2;                   //双亲节点的下标
        while (parent >= 0) {                           //从最后一个孩子节点的双亲节点一直向下调整到下标为0的节点
            shiftDown(parent, usedSize - 1);        //向下调整
            parent--;
        }
    }

    /**
     *
     * @param root 是每棵子树的根节点的下标
     * @param len  是每棵子树调整结束的结束条件
     * 向下调整的时间复杂度:O(logn)
     */
    private void shiftDown(int root,int len) {
        int parent = root;
        int child = parent * 2 + 1;
        while (child <= len) {                                              //若有两个孩子节点
            child = elem[child] < elem[child + 1] ? child : child + 1;      //找出两个孩子节点中的最大的节点
            if (elem[parent] > elem[child]) {                               //若孩子节点大于双亲节点
                swap(child, parent);                                        //交换孩子节点和双亲节点
                parent = child;                                             //将parent重置为child
                child = parent * 2;                                         //重置child,判断交换后的子树是否需要调整
            } else {
                break;                                                      //若无需调整,则直接跳出循环
            }
        }
    }


    /**
     * 入队:仍然要保持是小根堆
     * @param val
     */
    public void offer(int val) {
        if (isFull()) {
            grow();
        }
        if (usedSize == 0) {
            elem[0] = val;
            usedSize++;
            return;
        }
        elem[usedSize] = val;
        shiftUp(usedSize);
        usedSize++;
    }

    private void shiftUp(int child) {
        int parent = (child - 1) / 2;
        while (parent >= 0 && child != 0) {
            if (elem[child] < elem[parent]) {
                swap(child, parent);
                child = parent;
                parent = (child - 1) / 2;
            } else  {
                break;
            }
        }
    }

    private void swap(int x, int y) {
        int tmp = elem[x];
        elem[x] = elem[y];
        elem[y] = tmp;
    }

    public boolean isFull() {
        return usedSize == elem.length;
    }

    private void grow() {
        int len = elem.length;
        if (len < 64) {
            elem = Arrays.copyOf(elem, DEFAULT_INITIAL_CAPACITY * 2);
        } else {
            elem = Arrays.copyOf(elem, DEFAULT_INITIAL_CAPACITY + DEFAULT_INITIAL_CAPACITY >> 1);
        }
    }

    /**
     * 出队【删除】:每次删除的都是优先级高的元素
     * 仍然要保持是大根堆
     */
    public void pollHeap() {
        swap(0, usedSize - 1);
        usedSize--;
        shiftDown(0, usedSize - 1);
    }

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

    /**
     * 获取堆顶元素
     * @return
     */
    public int peekHeap() {
        return elem[0];
    }
}

以上就是优先级队列的全部内容了,感谢大家的收看,谢谢!!!!

如果觉得文章不错的话,麻烦大家三连支持以下ಠ_ಠ

制作不易,三连支持

谢谢!!!

以上的模拟实现代码未必是最优解,仅代表本人的思路,望多多理解,谢谢!!

最后送给大家一句话,同时也是对我自己的勉励:

生而有翼,怎么甘心匍匐一生,形如蝼蚁?

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

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

相关文章

oops-framework框架 之 启动流程(三)

引擎&#xff1a; CocosCreator 3.8.0 环境&#xff1a; Mac Gitee: oops-game-kit 回顾 上篇博客中我们通过 oops-game-kit 模版构建了基础的项目&#xff0c;另外讲解了下assets目录结构和游戏配置文件的基本使用相关&#xff0c;详情内容可参考&#xff1a; oops-framewo…

mysql5.7离线安装 windows

windows上离线安装mysql5.7 下载安装包 去官网下载对应版本的mysql官网 点击archives,接着选择自己要下载的版本&#xff0c;选择windows系统&#xff0c;并根据自己电脑的位数选择相应的版本【找到“此电脑”&#xff0c;鼠标右击&#xff0c;出来下拉框&#xff0c;选择“属性…

频率响应概述与波特图

频率响应的定义 在放大电路中&#xff0c;存在电抗元件&#xff08;如电容、电感&#xff09;、半导体管&#xff08;存在极间电容&#xff09;。由于电抗元件和极间电容的存在&#xff0c;当输入信号频率过高或过低时&#xff0c;不但放大倍数的数值会减小&#xff0c;而且将…

Python 3.x 快速安装 pip 包管理工具

目录 ℹ️ 1. 查看是否安装 pip1.1 方法一1.2 方法二 &#x1f6e0;️ 2. 安装方法2.1 通过 ensurepip 进行安装2.2 通过 get-pip.py 进行安装 参考文档&#xff1a; pip 官方安装文档&#xff1a;https://pip.pypa.io/en/stable/installation/ ℹ️ 1. 查看是否安装 pip 【…

最详细数据仓库项目实现:从0到1的电商数仓建设(数仓部分)

1、数仓 数据仓库是一个为数据分析而设计的企业级数据管理系统&#xff0c;它是一个系统&#xff0c;不是一个框架。可以独立运行的&#xff0c;不需要你参与&#xff0c;只要运行起来就可以自己运行。 数据仓库不是为了存储&#xff08;但是能存&#xff09;&#xff0c;而是…

hcia复习总结7

1&#xff0c;AR2发送2.0网段的信息给AR1&#xff0c;如果&#xff0c;AR1本身并不存在该网段的路由 信息&#xff0c;则将直接 刷新 到本地的路由表中。 Destination/Mask Proto Pre Cost Flags NextHop Interface 2.2.2.0/24 RIP 100…

Codeql复现CVE-2018-11776学习笔记

基本使用 1、首先下载struts2漏洞版本源码&#xff1a; https://codeload.github.com/apache/struts/zip/refs/tags/STRUTS_2_3_20 2、构建codeql数据库&#xff08;构建失败文末有解决办法&#xff09;&#xff1a; codeql database create ~/CodeQL/databases/struts2-2.3.…

unraid docker.img扩容

unraid 弹Docker image disk utilization of 99%&#xff0c;容器下载/更新失败 我的版本是6.11.5&#xff0c;docker.img满了导致容器不能更新&#xff0c;遇到同样问题的可以先用docker命令清除一下仓库(当然不一定能清理出来&#xff0c;我已经清理过只清理出来1G多点&…

存储卡乱码:原因、恢复与预防全攻略

一、存储卡乱码现象初现 在数字时代&#xff0c;存储卡已成为我们生活中不可或缺的一部分&#xff0c;无论是手机、相机还是其他电子设备&#xff0c;都离不开它的陪伴。然而&#xff0c;当我们在某一天突然发现存储卡上的文件出现了乱码&#xff0c;那种焦虑和困惑感简直无法…

Linux环境(Ubuntu)上的防火墙工具使用方法

目录 概述 1 防火墙工具&#xff08;ufw&#xff09; 1.1 安装防火墙工具&#xff1a; 1.2 操作防火墙相关命令 2 ufw操作命令的范例 2.1 打开/关闭防火墙 2.1.1 打开防火墙 2.1.2 关闭防火墙 2.1.3 查询当前防火墙状态 2.1.4 允许选择的端口访问 2.1.5 允许选择固定…

【海贼王的数据航海】排序——直接选择排序|堆排序

目录 1 -> 选择排序 1.1 -> 基本思想 1.2 -> 直接选择排序 1.2.1 -> 代码实现 1.3 -> 堆排序 1.3.1 -> 代码实现 1 -> 选择排序 1.1 -> 基本思想 每一次从待排序的数据元素中选出最小(或最大)的一个元素&#xff0c;存放在序列的起始位置&…

c++入门你需要知道的知识点(下)

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 今日主菜&#xff1a;c入门 主厨&#xff1a;邪王真眼 所属专栏&#xff1a;c专栏 主厨的主页&#xff1a;Chef‘s blog 前言&#xff1a; 上次我们通过c入…

C#构建类库

类库程序集能将类型组合成易于部署的单元&#xff08;DLL文件&#xff09;&#xff0c;为了使编写的代码能够跨多个项目重用&#xff0c;应该将他们放在类库程序集中。 一、创建类库 在C#中&#xff0c;构建类库是指创建一个包含多个类的项目&#xff0c;这些类可以被其他应用…

JavaScript进阶:js的一些学习笔记-4

文章目录 1. 拷贝1. 浅拷贝2. 深拷贝 2. 异常处理 1. 拷贝 这里指的拷贝是指拷贝引用类型的数据(对象) 1. 浅拷贝 拷贝对象&#xff1a;Object.assign() 或者 {…obj} 展开运算符 const obj {name:liuze,age:23 } const o {...obj}; o.age 22; console.log(o); console.…

华为WLAN配置攻击检测功能示例

华为WLAN配置攻击检测功能示例 组网图形 图1 配置攻击检测功能组网图 配置流程组网需求配置思路配置注意事项操作步骤配置文件 配置流程 WLAN不同的特性和功能需要在不同类型的模板下进行配置和维护&#xff0c;这些模板统称为WLAN模板&#xff0c;如域管理模板、射频模板、…

瑞熙贝通打造智慧校园实验室安全综合管理平台

一、建设思路 瑞熙贝通实验室安全综合管理平台是基于以实验室安全&#xff0c;用现代化管理思想与人工智能、大数据、互联网技术、物联网技术、云计算技术、人体感应技术、语音技术、生物识别技术、手机APP、自动化仪器分析技术有机结合&#xff0c;通过建立以实验室为中心的管…

LTE中的多址接入技术

OFDM&#xff08;Orthogonal Frequency Division Multiplexing&#xff09;正交频分复用技术&#xff0c;多载波调制的一种。将一个宽频信道分成若干正交子信道&#xff0c;将高速数据信号转换成并行的低速子数据流&#xff0c;调制到每个子信道上进行传输。 传统FDM:为避免载波…

Redisson 分布式锁原理分析

Redisson 分布式锁原理分析 示例程序 示例程序&#xff1a; public class RedissonTest {public static void main(String[] args) {Config config new Config();config.useSingleServer().setPassword("123456").setAddress("redis://127.0.0.1:6379"…

哔哩哔哩秋招Java二面

前言 作者&#xff1a;晓宜 个人简介&#xff1a;互联网大厂Java准入职&#xff0c;阿里云专家博主&#xff0c;csdn后端优质创作者&#xff0c;算法爱好者 一面过后面试官叫我别走&#xff0c;然后就直接二面&#xff0c;二面比较简短&#xff0c;记录一下&#xff0c;希望可以…

Docker基本配置及使用

Docker基本配置及使用 使用步骤 1.卸载旧版 代码如下&#xff1a;首先如果系统中已经存在旧的Docker&#xff0c;则先卸载 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engin…