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

news2025/1/16 14:04:25

成功就是失败到失败,也丝毫不减当初的热情 


目录

1.理解优先级队列

2.优先级队列的底层 

2.1 认识堆

2.1.1 堆的概念 

2.2.2 堆的存储 

2.2 堆的创建

2.2.1 向下调整算法

2.2.2 堆的创建

2.3 堆的插入

2.4 堆的删除 

2.5 查看堆顶元素

2.6 堆的运用 

3.PriorityQueue 

3.1  PriorityQueue 的特性

3.2 PriorityQueue 的构造方法 

3.2.1 无参构造方法

3.2.2 指定容量构造方法

3.2.3 用一个集合来创建优先级队列

3.3 PriorityQueue 常用的成员方法

3.4 优先级队列的运用 


1.理解优先级队列

在前面博文中我们学习了队列,那我们现在也知道了队列是遵循先进先出原则的一种数据结构。 那接下来我们就一起来学习优先级队列。

优先级队列(PriorityQueue):顾名思义我们应该就可以想到肯定是队列里面带有优先级,优先级高的数据优先出队。比如:我们平时玩手机的时候,要是有人给你打个电话,页面就会直接跳转到接电话的那个页面,那么也就说明手机来电话的优先级要比手机其他操作的优先级要高。

2.优先级队列的底层 

在 jdk1.8 版本中,优先级队列的底层其实就是一个堆。

2.1 认识堆

2.1.1 堆的概念 

堆的概念:堆具有结构性,也就是它是采用数组表示的完全二叉树。堆还具有有序性,也就是根节点大于子节点(或者小于子节点)。

通过根节点大于子节点(或小于子节点),又可以将堆分为大顶堆小顶堆

大顶堆:又称为最大堆,也就是树中所有父节点都要大于或等于子节点

小顶堆:又称为最小堆,也就是树中所有父节点都要小于或等于子节点 

2.2.2 堆的存储 

堆的存储:数据都是存储在数组中,完全二叉树是我们想象出来的

我们可以通过数组对应的下标,找出二叉树的父子关系:

左节点(leftChild)leftChild = parent * 2 + 1
右节点(rightChild)rightchild = parent * 2 + 1 + 1 = leftchild + 1
父节点(parent)parent =(child - 1)/  2

2.2 堆的创建

2.2.1 向下调整算法

向下调整算法前提:用向下调整算法有一个前提就是左右子树必须都是堆,要是建大顶堆左右子树必须都是大顶堆,要是建小顶堆左右子树必须都是小顶堆。

例:建小顶堆,使用向下调整算法,首先左右子树都必须是小顶堆

第一步,先判断左右子树是否都为小顶堆,要是为小顶堆就可以进行第二步向下调整算法

第二步,向下调整算法,找出根结点的左子树和右子树小的那个,然后与根结点比较如果小于根结点就交换(如果不小于,则这整颗数都是小堆不需要交换)。依次循环,直到找到叶子结点终止

例:现在有一个数组里面的元素为{27,15,19,18,26,37},现在需要将它进行向下调整,成为小顶堆

首先我们将它想成一棵完全二叉树 :

通过图片,我们可以发现根节点的左右子树都是小根堆,那么接下来我们可以直接使用向下调整算法,使完全二叉树变为小根堆。首先将数组和需要调整节点的下标传给 shiftDown 方法的形参数组 array 和整型 parent ,然后我们可以通过 parent 找到左子节点将左子节点下标存放在 child 变量中。然后判断存放在 child 变量中的左子节点是否存在,如果存在我们就进入循环。然后判断右子节点是否存在,如果存在则判断右子节点是否比左子节点小,要是小就让child存放右子节点的下标。然后在判断 parent 下标对应的值是否小于 child 下标对应的值,如果小于就交换,然后一值循环,直到不小于或者child下标不存在时跳出循环,此时便是小根堆了

//向下调整,建小顶堆,arr是存放数据的数组,size是有效元素的个数
    public void shiftDown(int[] arr, int parent) {
        int child = parent * 2 + 1;
        while (child < size) {
            if (child + 1 < size && arr[child + 1] < arr[child]) {
                child += 1;
            }
            if (arr[parent] <= arr[child] ) {
                break;
            } else {
                int tmp = arr[parent];
                arr[parent] = arr[child];
                arr[child] = tmp;
                parent = child;//交换之后需要接着向下调整
                child = parent * 2 + 1;
            }
        }
    }

2.2.2 堆的创建

上面讲解了向下调整算法,如果建大顶堆那么左右子树必须都是大顶堆,如果建小顶堆那么左右子树必须都是小顶堆,这种情况下才能用向下调整算法。

那么对普通的序列,根节点的左右子树不满足堆的特性,又该如何调整呢? 

例: 现在有一个数组里面的元素为 {1,5,3,8,7,6},现在需要将其成为大顶堆

首先我们需要找到最后一个非叶子节点 ,那么它的左右子树肯定是叶子节点,叶子节点没有子节点那么它们就是堆,那我们就可以调用向下调整算法,那么此时最后一棵非叶子节点也就变成了堆,然后我们就依次将每个非叶子节点都通过向下调整算法变成堆,那么此时通过向下调整的非叶子节点也都变成了堆,一直调整到根节点即可变成堆

//建堆,arr是存放数据的数组,size是有效元素的个数
    public void buildHeap(int[] arr) {
        if (arr == null) {
            return;
        }
        int lastNoLeafNode = (size - 1 - 1) / 2;
        while (lastNoLeafNode >= 0) {
            shiftDown(arr,lastNoLeafNode);
            lastNoLeafNode--;
        }
    }

    //向下调整,建大顶堆
    public void shiftDown(int[] arr, int parent) {
        int child = parent * 2 + 1;
        while (child < size) {
            if (child + 1 < size && arr[child + 1] > arr[child]) {
                child += 1;
            }
            if (arr[parent] >= arr[child] ) {
                break;
            } else {
                int tmp = arr[parent];
                arr[parent] = arr[child];
                arr[child] = tmp;
                parent = child;
                child = parent * 2 + 1;
            }
        }
    }

2.3 堆的插入

堆的插入操作,也就是将元素插入到堆中的操作,插入后依然满足堆的性质 

堆的插入的步骤:

  • 第一步: 先将元素插入到数组最后一个元素的后面(注:插入前需要判断是否需要扩容)
  • 第二步:将插入的元素依次向上调整,直到满足堆的性质 

例如:我们现在有一个数组,数组中的元素为{1,5,3,8,7,6}:

从这个数组和想象出来的完全二叉树我们也就可以看出它是小顶堆,那么现在我们需要插入一个元素 2,应该怎么做呢?

第一步:判断是否需要扩容,然后将 2 插入到数组最后一个元素的后面 

第二步:将插入的元素依次向上调整,直到满足堆的性质 

 注:既然在未插入之前它本来就是个小堆,那么插入得时候只需向上调整即可

//插入,arr 是存储数据的数组,size 是有效元素个数
    public void insert(int num) {
        if (size >= arr.length) {
            grow();
        }
        arr[size++] = num;
        int last = size - 1;
        shiftUp(last);
    }

    //扩容
    private void grow() {
        arr = Arrays.copyOf(arr,arr.length * 2);
    }

    //向上调整
    public void shiftUp(int child) {
        int parent = (child - 1) / 2;
        while (child > 0) {
            //因为本来就是一个小根堆,所以只需将插入得值与父节点的值比较
            if (arr[child] < arr[parent]) {
                int tmp = arr[child];
                arr[child] = arr[parent];
                arr[parent] = tmp;

                child = parent;
                parent = parent = (child - 1) / 2;
            } else {
                break;
            }
        }
    }

2.4 堆的删除 

堆的删除:一定删除的是堆顶元素 

堆的删除步骤: 

  • 第一步:将堆中最后一个元素赋值给堆顶元素 
  • 第二步:将堆中的有效元素减一
  • 第三步:将堆顶元素向下调整

 例如:我们现在有一个数组,数组中的元素为{1,5,3,8,7,6}:

我上面的数组以及图我们可以发现这是一个小顶堆,那么我们现在需要删除它的堆顶元素,删除之后它依然是一个小顶堆,应该怎么做呢? 

第一步,将最后一个元素赋值给堆顶元素,有效元素个数减一,就相当于数组中删除了一个元素 

第二步,将堆顶元素进行向下调整 

//删除,arr 是存储数据的数组,size 是有效元素个数
    public int poll() {
        int tmp = arr[0];
        arr[0] = arr[--size];
        shiftDown(arr,0);
        return tmp;
    }
//向下调整,建小顶堆
    public void shiftDown(int[] arr, int parent) {
        int child = parent * 2 + 1;
        while (child < size) {
            if (child + 1 < size && arr[child + 1] < arr[child]) {
                child += 1;
            }
            if (arr[parent] <= arr[child] ) {
                break;
            } else {
                int tmp = arr[parent];
                arr[parent] = arr[child];
                arr[child] = tmp;
                parent = child;//交换之后需要接着向下调整
                child = parent * 2 + 1;
            }
        }
    }

2.5 查看堆顶元素

直接返回堆顶值即可 

//查看堆顶元素
    public int pook() {
        return arr[0];
    }

关于堆操作的代码,阿紫姐姐写的并不是很完善,大家可以自行完善一下,比如每次对堆进行删除和插入之前都进行判空一下等等。 

2.6 堆的运用 

优先级队列的底层其实也就是堆,那我们学习了堆,其实也就相当于学习了优先级队列 

堆可以用来进行排序:

  • 升序:建大堆
  • 降序:建小堆 

后面的排序博文中会讲堆排序 ,大家目前先不用管堆排序

3.PriorityQueue 

3.1  PriorityQueue 的特性

在Java集合框架中其实提供了两种类型的优先级队列分别是 PriorityQueue 和 PriorityBlockingQueue

  • PriorityQueue:线程不安全
  • PriorityBlockingQueue是线程安全的 

那我们本次主要讲解 PriorityQueue

当我们在使用 Java 自带的集合 PriorityQueue 时,需要注意: 

  • 使用 PriorityQueue 时导入PriorityQueue 所在的包
  • PriorityQueue 中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException 异常 
  • PriorityQueue 中不能插入null对象,否则会抛出 NullPointerException 
  • PriorityQueue 底层使用了堆数据结构 
  • PriorityQueue 默认情况下是小堆 

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

用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可 

import java.util.Comparator;
class IntCmp implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2-o1;
    }
}

3.2 PriorityQueue 的构造方法 

目前我们只介绍 PriorityQueue 常用的几种构造方法 

3.2.1 无参构造方法

创建一个空的优先级队列,默认容量是 11 

3.2.2 指定容量构造方法

创建一个初始容量为 initialCapacity 的优先级队列

注意: initialCapacity 不能小于1,否则会抛 IllegalArgumentException 异 常 

3.2.3 用一个集合来创建优先级队列

用一个集合来创建优先级队列 

3.3 PriorityQueue 常用的成员方法

方法名功能介绍
boolean offer(E e)插入元素,插入成功返回 true ,否则返回 false。如果 e 对象为空,抛出 NullPointerException 异常。空间不够会扩容
E peek()查看堆顶元素并返回,如果优先级队列为空则返回null
E poll()删除堆顶元素并返回,如果优先级队列为空则返回null
int size()获取优先级队列有效元素的个数
void clear()清空优先级队列里面的元素
boolean isEmpty()判断优先级是否为空,为空返回true,否则返回false

在JDK 1.8中,PriorityQueue的扩容方式:

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

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

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

3.4 优先级队列的运用 

优先级队列经常用来解决 top-k 的问题。比如:班级前三的同学

接下来我们就用 top-k 完成一道面试题

面试题:最小 k 个数(题目来源:力扣)

题目描述:设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

输入: arr = [1,3,5,7,2,4,6,8], k = 4 

输出: [1,2,3,4]

思路:题目需要返回数组中的最小的 k 个数,那么我们就可以把数组的前 k 个数构成一个大堆,然后从第 k 个下标开始,依次跟堆顶比较,如果小于堆顶,就删除堆顶的元素,然后插入第 k 下标对应的元素,整个数组都与堆顶比较完后,那么堆里面剩下的就是数组中最小的 k 个数了 

import java.util.Comparator;
import java.util.PriorityQueue;

public class Test {
    public static void main(String[] args) {
        int[] arr = {1,3,5,7,2,4,6,8};
        int k = 4;
        int[] arrTmp = smallestK(arr,k);
        for (int i = 0; i < arrTmp.length; i++) {
            System.out.print(arrTmp[i]+" ");
        }
    }
    public static int[] smallestK(int[] arr, int k) {
        int[] retArr = new int[k];
        for (int i = 0; i < retArr.length; i++) {
            retArr[i] = arr[i];
        }
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new IntCmp());
        for (int i = 0; i < k; i++) {
            priorityQueue.offer(retArr[i]);
        }
        int t = k;
        while (t < arr.length) {
            if(priorityQueue.isEmpty()) {
                break;
            }
            int tmp = priorityQueue.peek();
            if (arr[t] < tmp) {
                priorityQueue.poll();
                priorityQueue.offer(arr[t]);
            }
            t++;
        }
        for (int i = 0; i < k; i++) {
            if (priorityQueue.isEmpty()) {
                break;
            }
            retArr[i] = priorityQueue.poll();
        }
        return retArr;
    }
}

class IntCmp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;
    }
}

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

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

相关文章

windows 11 安装jdk1.8

1.先去JDK官网下载 JDK1.8官网 2.进入到官网之后 3. 点击上图windows选项       按照你的电脑是32位还是64位按需下载(我电脑是64位) 4. 点击下载之后就会跳转到Oracle账号登录界面&#xff08;没有Oracle账号的注册一下这边我就省略了注册了&#xff09; 5.把下载好的…

商业智能BI财务分析,如何从财务指标定位到业务问题

商业智能BI开发人员都会思考如何从财务指标定位到业务问题&#xff0c;就是做了很多的商业智能BI开发&#xff0c;每次也都涉及到了财务分析&#xff0c;各种财务能力指标&#xff0c;各种可视化的分析图表。但是不知道这些财务指标到底能够反映出企业的什么问题&#xff0c;和…

蓝桥杯Python练习题3-十六进制转八进制

资源限制 内存限制&#xff1a;512.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 给定n个十六进制正整数&#xff0c;输出它们对应的八进制数。 输入格式 输入的第一行为一个正整数n &#xff08;1<n<10&am…

Weston 纹理倒置(render-gl)

纹理倒置 背景 在 render-gl 接入 frame buffer object 实现 off-screen 渲染后,发现得到的渲染图发生了180的倒置. 查阅了有关资料后,在 eglspec.1.5 中的 2.2.2.1 Native Surface Coordinate Systems 找到了答案: The coordinate system for native windows and pixmaps …

2023届毕业生职场第一步:挡飞刀

这篇博客不会教你某一段代码怎么写&#xff0c;某一个知识点怎么入门&#xff0c;但却可以让你在2023年的职场上&#xff0c;躲避飞刀。 目录 1、啥是挡飞刀 2、其他知名大厂也好不到哪里去 3、 毕业生如何躲飞刀&#xff1f; 4、毕业生首选什么样的公司 5、不建议去这样的…

工具学习——ubuntu轻量桌面对比

因为最近要做一些ubuntu上的开发&#xff0c;然后使用ssh问题是经常会出现中断&#xff0c;虽然可以使用等tmux方法来挂起进程&#xff0c;但是感觉不如界面方便&#xff0c;然后现在问题来了&#xff0c;我的ubuntu服务器是一个双核的性能很差内存也少的机器&#xff0c;我怎么…

13-Golang中for循环的用法

Golang中for循环的用法for循环基本语法for循环流程图注意事项和使用细节for循环 就是让一段代码循环的执行。 基本语法 for循环变量初始化&#xff1b;循环条件&#xff1b;循环变量迭代{循环操作(语句)}package main import "fmt"func main(){for i : 1; i < …

C#,谷歌(Google)CityHash64与CityHash128散列哈希算法的C#源程序

1、CityHash简史 Google 2011年发布了 CityHash 系列字符串散列算法 。今天发布的有两种算法&#xff1a;CityHash64 与 CityHash128 。它们分别根据字串计算 64 和 128 位的散列值。这些算法不适用于加密&#xff0c;但适合用在散列表等处。 Google 一直在根据其数据中心常…

“刀片嗓”“水泥鼻”“咳出肺”可以这样缓解!

很多人感染新冠后&#xff0c;咽痛、鼻塞、干咳和其他不适&#xff0c;非常不舒服&#xff0c;在网上讨论也总结了“刀片嗓”、“水泥鼻”、“咳出肺”三个字生动地展现了他们的不适。今天&#xff0c;对于这三种症状&#xff0c;今天就为大家带来一些缓解的小方法。 病症一&am…

机器学习中的评价指标

1.MSE&#xff08;mean squared error&#xff09; 叫做均方误差&#xff0c;又称L2损失。取平方有一个特性&#xff0c;它惩罚更大的错误更多&#xff08;毕竟都取平方了&#xff09;。方差一般用来计算样本的离散程度&#xff0c;而均方误差则可以用做衡量模型拟合的一个度量…

Linux串口编程详解(阻塞模式、非阻塞模式、select函数)

前言&#xff1a;之前一直觉得串口编程很简单&#xff0c;这两天仔细研究后发现串口里的各种参数还挺复杂&#xff0c;稍不注意就容易出错&#xff0c;这里总结一下网上的各种文章及自己的理解与实践。 open 函数 功能描述&#xff1a;用于打开或创建文件&#xff0c;成功则返…

【05】概率图表示之马尔可夫随机场

概率图表示之马尔可夫随机场 文章目录马尔可夫随机场正式定义与贝叶斯网络的比较马尔可夫随机场中的独立性条件随机场示例正式定义示例&#xff08;续&#xff09;CRF特性因子图贝叶斯网络可以以一种紧凑的方式表示许多概率分布。然而&#xff0c;我们在前一章中已经看到&…

笔试强训(8)

笔试题1:密码强度等级密码强度等级_牛客题霸_牛客网 在这个题中: 1)统计密码的长度直接通过length()来进行解决 2)统计大写字母和小写字母的个数分别在函数里面定义两个变量来进行解决 3)统计数字和富豪也是分别用两个变量来进行保存 import java.util.Scanner;// 注意类名必须…

记一次 Maven 打包后,第三方无法使用的排查记录

你好&#xff0c;我是悟空。 本文主要内容如下&#xff1a; 前言 最近遇到一个需求&#xff1a; 写一个工具类的 JAR 包&#xff0c;然后提供给第三方使用。 期间遇到了一些问题&#xff1a; 第三方引入 JAR 包后&#xff0c;无法 import。第三方引入 JAR 包后&#xff0c…

你可以不看世界杯,但你一定要知道这些

2022卡塔尔世界杯&#xff0c;阿根廷夺冠&#xff0c;举世沸腾。 ​夺冠之路&#xff0c;遍布荆棘 时隔36年&#xff0c;阿根廷再次夺得大力神杯&#xff0c;回望夺冠之路&#xff0c;坎坷遍布&#xff0c;荆棘丛生。 2006年夏天&#xff0c;梅西第一次参加世界杯。时任阿根廷…

Mybatis源码(一)获取数据源

前言 Mybatis做为一种半ORM框架&#xff08;半&#xff1a;需要手动写sql&#xff09;。ORM&#xff08;Object Relational Mapping&#xff09;的技术本质是&#xff1a;ORM框架将对象的值 映射到 对应数据库类型&#xff1a; 如 String -> varchar。 且mybatis分为两种实…

数字孪生核电站促进界面监测的应用实践

未来核电站将向着数字化、智能化发展,“少人值守、智能监测”会广泛应用于核电站运行管理。利用数字孪生技术,可以对实体核电站和孪生核电站的数据进行交换分析,促进核电站的运行管理和监测,更好地确保反应堆运行安全。 数字孪生核电站促进界面监测的应用实践 北京智汇云舟科技…

h5中使用微信分享

1.需要 绑定域名&#xff1a; 先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”&#xff08;即访问前端项目对应的域名)。 2.在项目中引入sdk: 在需要调用 JS 接口的页面引入如下 JS 文件&#xff0c;&#xff08;支持https&#xff09;&#xff…

搭vue项目(初级版)

这个项目只有 会员管理和直播管理两个模块。创建两个模块是为了验证一下路由跳转。 下载项目&#xff1a; 可以到我的资源中下载压缩包 或者 git clone 前端项目搭建: 前端搭建的项目&#xff0c;仅供学习使用 1.安装vue-cli,全局只需要安装一次&#xff0c;如果安装过一次&am…

MyBatisPlus ---- 入门案例

MyBatisPlus ---- 入门案例1. 开发环境2. 创建数据库及表a>创建表b>添加数据3. 创建SpringBoot工程a>初始化工程b>引入依赖c>idea中安装lombok插件4. 编写代码a>配置application.ymlb>启动类c>添加实体d>添加mappere>测试f>添加日志1. 开发环…