优先级队列(堆)

news2024/12/29 8:51:25

1.优先级队列

1.1概念

前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但是有些情况下,操作的数据可能带有优先级时,可能需要优先级高的元素先入队列,该场景中,使用队列显然不合适,比如在手机上玩游戏的时候,如果有来电,我们应当让系统先处理打进来的电话;在学校排座位的时候,可以先让部分成绩好的选作为,再让从低到高的同学选座位。

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

2.优先级队列的模拟实现

JDK1.8的PreorityQueue底层使用了堆这种数据结构,而堆实际就是在完成完全二叉树的基础上进行了一些调整。

2.1堆的概念

如果有一个关键码的集合K={k0,k1,k2,...,kn-1},把它的所有元素按照完全二叉树的顺序储存方式储存在一个一维数组中,并满足ki<=k2i+1且ki<=k2i+2或ki>=k2i+1且ki>=k2i+2,i=0,1,2...,则称为小堆或大堆。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

【堆的性质】

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

  • 堆总是一颗完全二叉树。

2.2堆的储存方式

从堆的概念可知,堆是一颗完全二叉树,因此可以层序的规则采取顺序的方式来高效储存

【注意】对于 非完全二叉树,则不适用使用顺序方式来储存,因为普通二叉树的有很多的节点是null,我们需要表示出来一些位置为空,这将会导致对于 空间的利用率很低,有很多的空间只是为了占位置。

将元素储存到数组中后,可以根据二叉树章节的性质对树进行还原。假设i为节点在数组中的下标,则有

  • 如果i为0,则i表示的节点为根节点,否则i的双亲节点为(i-1)/2;

  • 如果2*i+1小于节点个数,则节点i的左孩子节点下标是2*i+1,否则没有左孩子;

  • 如果2*i+2小于节点个数,则则节点i的右孩子节点下标是2*i+2,否则没有右孩子。

2.3堆的创建

2.3.1堆向下调整

对于集合{27,15,19,18,28,34,65,49,25,37}中的数据,我们如何将其创建成堆呢?(以创建小根堆为例)

向下调整过程(以小根堆为例):

  • 让parent标记需要调整的节点,child标记parent的左孩子(注意:我们要判断是否有左孩子)

  • 如果parent的左孩子存在,即child<size,进行以下操作,直到parent的左孩子不存在

  1. parent的右孩子是否存在,存在找到左右孩子中最小的孩子,用child进行标记;

  1. 将parent与较小的孩子进行child进行比较,看是否需要调整:如果调整,我们进行调整后,让parent=child,在进行同样的循环,直到不交换元素或没有孩子节点。

public class Test {
    //向下调整,小根堆
    public void shiftDown(int[] array, int parent) {
        int size = array.length;
        if(size == 0) {
            return;
        }
        int child = parent * 2 + 1;
        int tmp;
        while(child < size) {
            if(child + 1 < size && array[child] > array[child + 1]) {
                child++;
            }
            if(array[child] < array[parent]) {//孩子小,进行交换
                tmp = array[parent];
                array[parent] = array[child];
                array[child] = tmp;
                parent = child;
                child = parent * 2 + 1;
            }else {
                break;
            }
        }
    }

    public static void main(String[] args) {
        Test t = new Test();
        int[] array = new int[]{ 27,15,19,18,28,34,65,49,25,37 };
        t.shiftDown(array, 0);
        System.out.println(Arrays.toString(array));
    }
}

注意在调整以parent为根的二叉树时,必须要满足parent的左子树和右子树已经是堆了才可以向下调整

最坏的情况:从根一路比较到叶子,比较的次数为完全二叉树的高度,即时间复杂度为O(log2N)

2.3.2堆的创建

对于普通的队列,如{1,5,3,8,7,6},即根节点的左右子树不满足堆的特性,又该如何调整呢?

//堆的创建
    public void createHeap(int[] array) {
        //找到第一个要调整的父节点
        int root = ((array.length - 2) >> 1);
        for (int i = root; i >= 0; i--) {
            shiftDown(array, i);
        
    }
public static void main(String[] args) {
        int[] array1 = new int[]{1,5,3,8,7,6 };
        t.createHeap(array1);
        System.out.println(Arrays.toString(array1));
    }

2.3.3建堆的时间复杂度

为了简化,我们使用满二叉树来进行时间复杂度的计算。

2.4根的插入和删除

2.4.1堆的插入

堆的插入总共需要两个步骤:

  1. 先将元素放入到底层的空间中(注意:空间不足时要进行扩容);

  1. 将最后新插入的元素向上调整,直到满足堆的性质。

//向上调整 小根堆
    public void shiftUp(int[] array, int child) {
        if(child <= 0) {
            return;
        }
        int parent;
        while(child > 0) {
            parent = (child - 1) / 2;
            if(array[child] < array[parent]) {
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
                child = parent;
            }
            else {
                return;
            }
        }
    }

【解释】我们如果在小根堆中,再放入一个元素,我们使用向上调整即可,再次获得小根堆。

2.4.2堆的删除

【注意】堆的删除一定是堆顶的元素(最大值或最小值)。具体如下:

  1. 将堆顶元素对堆中最后一个元素交换;

  1. 将队中有效数据个数减少一个;

  1. 堆顶元素进行向下调整。

2.5用堆模拟实现优先级队列

package Demo;

import java.util.Arrays;

/**
 * Describe:模拟实现优先级队列
 * User:lenovo
 * Date:2023-01-25
 * Time:19:51
 */
public class MyPriorityQueue {
    private int[] array = new int[11];//在实际中也是默认为11;
    private int size = 0;

    //插入元素
    public void offer(int e) {
        if(size == array.length) {
            if(size == 0) {
                array[0] = e;
                size++;
                return;
            }
            else{
                array = Arrays.copyOf(array, 2*array.length);
            }
        }
        array[size++] = e;
        //向上调整
        shiftUp(array, size - 1);
    }
    private void shiftUp(int[] array, int child) {
        if(child <= 0) {
            return;
        }
        int parent;
        while(child > 0) {
            parent = (child - 1) / 2;
            if(array[child] < array[parent]) {
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
                child = parent;
            }
            else {
                return;
            }
        }
    }

    //删除元素
    public int poll() {
        if(size == 0) {
            throw new RuntimeException("优先级队列为空");
        }
        int ret = array[0];
        size--;
        array[0] = array[size];
        shiftDown(array, 0);
        return ret;
    }
    //向下调整,小根堆
    private void shiftDown(int[] array, int parent) {
        int size = array.length;
        if(size == 0) {
            return;
        }
        int child = parent * 2 + 1;
        int tmp;

        while(child < size) {
            if(child + 1 < size && array[child] > array[child + 1]) {
                child++;
            }
            if(array[child] < array[parent]) {//孩子小,进行交换
                tmp = array[parent];
                array[parent] = array[child];
                array[child] = tmp;
                parent = child;
                child = parent * 2 + 1;
            }else {
                break;
            }
        }
    }

    //查看堆顶元素
    public int peek() {
        if(size == 0) {
            throw new RuntimeException("优先级队列为空");
        }
        return array[0];
    }

    //开始检测
    public static void main(String[] args) {
        MyPriorityQueue a = new MyPriorityQueue();
        a.offer(1);
        a.offer(2);
        a.offer(3);
        System.out.println(a.poll());
        System.out.println(a.poll());
    }
}

常见习题

1. 下列关键字序列为堆的是()
A: 100,60,70,50,32,65 B: 60,70,65,50,32,100 C: 65,100,70,32,50,60
D: 70,65,100,32,50,60 E: 32,50,100,70,65,60 F: 50,100,70,65,60,32
2. 已知小根堆为8,15,10,21,34,16,12,关键字8删除后重新建队,在这过程中需要比较的次数是(3)
3. 最小堆[0,3,2,5,7,4,6,8],在删除栈顶元素0之后,其结果是([2,3,4,5,7,8,6])

3.常见的接口

3.1PriorityQueue的特性

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

关PriorityQueue的使用要注意:

  1. 使用时必须要导入PriorityQueue所在的包,即:

import java.util.PriorityQueue;
  1. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则就会抛出ClassCastException异常

  1. 不能插入null对象,否则就会抛出NullPointerException异常

  1. 没有容量的限制,可以插入任意多个元素,其内部空间可以自动扩容

  1. 插入和删除元素的时间复杂度都是O(log2N);

  1. PriorityQueue底层使用了堆数据结构;

  1. PriorityQueue默认情况下是小堆——即每次获取到的元素都是最小的元素;

3.2 ProrityQueue常用接口介绍

1.优先级队列的构造

此处只介绍几种常用的构造方法:

构造器

功能

PriorityQueue()

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

PriorityQueue(int

initialCapacity)

创建一个初始容量为initialCapacity的优先级队列,注意:initicalCapacity不能小于1,否则将会抛出IIIegalArgumentException异常

PriorityQueue(Collection<? extends E> c)

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

public static void main(String[] args) {
        //创建一个空的优先级队列,默认容量为11
        PriorityQueue<Integer> q1 = new PriorityQueue<>();
        //创建一个空的优先级队列,容量为给定值
        PriorityQueue<Integer> q2 = new PriorityQueue<>(100);//大小为100

        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);

        //用ArrayList对象来构造一个优先级队列的对象
        //q3中已经包含了三个元素
        PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
        System.out.println(q3.size());
        System.out.println(q3.peek());
    }

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

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

//直接实现Comparator接口,来实现大根堆
class IntCmp implements Comparator<Integer> {
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;
    }
}

public class Test {
    public static void main(String[] args) {
        PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
        p.offer(1);
        p.offer(2);
        p.offer(3);
        p.offer(4);
        p.offer(5);

        System.out.println(p.peek());
    }
}

我们直接将在创建优先级队列的时候,把比较器给它就行;

此时创建出来的就是一个大根堆。

2.插入/删除/获取优先级队列

函数名

功能介绍

boolean offer(E e)

插入元素e,插入成功返回true,如果e对象为空,将抛出NullPointerException异常,时间复杂度O(log2N),注意空间不够时候会进行自动扩容。

E peek()

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

E pool()

移除优先级最高的元素并返回,如果优先级队列为空,返回null

int size()

获取有效元素的个数

void clear()

清空所有内容

boolean isEmpty()

检测优先级队列是否为空,空返回true

优先级队列的扩容说明:

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

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

  • 如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容的。(大概为21亿多)

3.3 OJ练习

获取堆中前k个最大值(或最小值)。(时间复杂度尽可能的低)

import java.util.PriorityQueue;
class IntCmp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;
    }
}
class Solution {
    public int[] smallestK(int[] arr, int k) {
    PriorityQueue<Integer> q = new PriorityQueue<>(new IntCmp());
        int[] ret = new int[k];
        if(arr == null || k <= 0) {
            return ret;
        }
        for (int i = 0; i < k; i++) {
            q.offer(arr[i]);
        }
        int size = arr.length;
        for (int i = k; i < size; i++) {
            if(q.peek() > arr[i]) {
                q.poll();
                q.offer(arr[i]);
            }
        }
        int i = 0;
        while(!q.isEmpty()) {
            ret[i++] = q.poll();
        }
        return ret;
    }
}

【解析】

  • 我们以前k个最小值举例;

  • 我们需要的是K个元素大小的空间,用于存放最小值(我们并不需要多大空间,再多的空间实际上我们并用不到后面的数据。我们使用大根堆进行存放,大根堆里存放最小值,arr[0]是最小值中的最大值,我们只需要和它比较即可。

  • 我们先存放前k个元素,放入堆中;

  • 数组后面的元素,我们都要与堆的首元素进行比较(这是堆的最大的数);如果q.peek()>array[i],我们删除首元素,插入array[i];反之,无操作

4.堆的应用

4.1ProrityQueue的实现

用堆最为底层结构封装优先级队列

4.2 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤

  1. 建堆

  • 升序:建大堆

  • 降序:建小堆

2.利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握向下调整,就可以完成堆排序

【常见习题】

,1.一组记录排序码为(5,11,7,2,3,12),利用堆排序的方法建立的初始堆为(17,11,7,2,3,5)

4.3 Top-k问题

TOP-K问题:即数据集合中前K个最大的元素或者最小的元素,一般情况下数据量都是比较大(3.3中有讲)

比如:世界500前、富豪榜、游戏中前100的活跃玩家等

对于Top-K问题,能想到的最简单方法是直接排序,但是数据量过大,排序是很麻烦的。最佳的方式就是用堆来解决,基本思路(上面3.3)

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

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

相关文章

7、数据类型转换

目录 一、隐式类型转换 二、显示类型转换 类型转换是将一个值从一种类型更改为另一种类型的过程。例如&#xff0c;可以将String类型的数据“457”转换为数值型&#xff0c;也可以将任意类型的数据转换为String类型。 一、隐式类型转换 从低级类型向高级类型的转换&#xf…

一起自学SLAM算法:10.1 RTABMAP算法

连载文章&#xff0c;长期更新&#xff0c;欢迎关注&#xff1a; 同前面介绍过的大多数算法一样&#xff0c;RTABMAP也采用基于优化的方法来求解SLAM问题&#xff0c;系统框架同样遵循前端里程计、后端优化和闭环检测的三段式范式。这里重点讨论RTABMAP两大亮点&#xff0c;一个…

python实现问卷星自动填写(可以跳过智能验证)

最近有一个社会实践需要做问卷调查&#xff0c;突发奇想搞一个自动化脚本&#xff0c;省事省米 1 下载依赖selenium selenium是一款网页爬虫重要的工具。 2 安装chrome驱动 这里需要准备chrome浏览器以及对应的驱动。需要注意的是驱动的版本需要和chrome保持一致。 chrome…

Mybatis框架介绍及使用

文章目录1. 概述1.1什么是框架1.2 Mybatis是什么1.3 Mybatis流程分析2. 参数的使用2.1 保存时获取插入id2.2 #{}与${}的区别2.3 parameterType 的使用2.4 SqlMapConfig.xml 中配置的内容3. 动态SQL3.1 <if>标签3.2 <where>标签3.3 <foreach > 标签3.4 抽取重…

ipv6内网穿透,有ipv6地址外网无法访问

问题描述 光猫已经开启ipv6&#xff0c;并且电脑/服务器已经有ipv6地址&#xff0c;只能通过ipv6局域网访问&#xff0c;外网/手机流量访问设备 原因分析&#xff1a; 光猫没有关闭防火墙路由器入站防护 解决方案&#xff1a; 1.光猫没有关闭防火墙 这里以中国移动的光猫为…

(17)目标检测算法之 YOLOv8 算法改进详细解析

目标检测算法之 YOLOv8 算法改进详细解析 1.YOLO的一些发展历史 YOLOv1&#xff1a;2015年Joseph Redmon和 Ali Farhadi等 人&#xff08;华盛顿大学&#xff09; YOLOv2&#xff1a;2016年Joseph Redmon和**Ali Farhadi等人*&#xff08;华盛顿大学&#xff09;* YOLOv3&am…

【JavaEE】多线程之线程安全(volatile篇),wait和notify

目录 内存可见性问题 volatile关键字 从JMM的角度来看内存可见性 wait和notify wait notify-notifyAll 内存可见性问题 首先运行一段代码&#xff0c;线程t1 用 Mycount.flag 作为标志符&#xff0c;当不为0的时候就跳出循环&#xff0c;线程t2 通过输入来改变 Mycount.f…

springboot 入门

springboot是什么 传统的开发模式下&#xff0c;无论是基于xml或注解&#xff0c;都要做许多配置&#xff0c;如果项目中集成越多的其他框架&#xff0c;配置内容也会越多。为了让开发人员以最少的配置去开发应用&#xff0c;springboot诞生了。springboot的原则是约定大于配置…

VSCode中4个Settings(JSON)的区别与联系

目录 &#x1f525; 前言 1. Preferences: Open Default Settings(JSON) 2. Preferences: Open User Settings 3. Preferences: Open Settings(JSON) 4. Preferences: Open Workspace Settings(JSON) &#x1f525; 总结 &#x1f525; 前言 在VSCode中输入快捷键ctrlsh…

读书笔记:梯度法求函数的最小值 gradient_method.py ← 斋藤康毅

● 由多元函数全部变量的偏导数汇总而成的向量称为梯度&#xff08;gradient&#xff09;。梯度指示的方向是各点处的函数值减小最多的方向。● 虽然梯度的方向并不一定指向最小值&#xff0c;但沿着它的方向能够最大限度地减小函数的值。因此&#xff0c;在寻找函数的最小值&a…

一篇五分生信临床模型预测文章代码复现——Figure 8 生存曲线鲁棒性分析

之前讲过临床模型预测的专栏,但那只是基础版本,下面我们以自噬相关基因为例子,模仿一篇五分文章,将图和代码复现出来,学会本专栏课程,可以具备发一篇五分左右文章的水平: 本专栏目录如下: Figure 1:差异表达基因及预后基因筛选(图片仅供参考) Figure 2. 生存分析,…

Linux输入子系统简析

1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. 背景 本文基于 Linux 4.14 内核源码进行分析。 3. 简介 Linux 内核输入子系统&#xff0c;负责对系统中的输入设备进行管理。 一方面&#xf…

Spring Cloud_Ribbon负载均衡服务调用

目录一、概述1.是什么2.官网资料3.能干嘛二、Ribbon负载均衡演示1.架构说明2.POM三、Ribbon核心组件Irule1.IRule2.如何替换3.新建package&#xff08;注意&#xff1a;包的位置&#xff09;4.上面包下新建MySelfRule规则类5.主启动类添加RibbonClient6.测试四、Ribbon负载均衡…

Python将JSON格式文件导入 redis,多种方法

在导入前需要先确定你已经安装 Redis&#xff0c;并且可以启动相关服务。 windows 上启动 redis 的命令是 redis-server.exe redis.windows.conf&#xff0c;效果图如下&#xff1a; 文章目录使用 Python 连接 redis安装 redis 与 导入使用代码连接 Redis写入键值操作 JSON 文…

第26章 分布式缓存数据库配置的定义实现

1 Core.Configuration.CacheConfig namespace Core.Configuration { /// <summary> /// 【缓存配置--类】 /// <remarks> /// 摘要&#xff1a; /// 通过该类中的属性成员实例对“appsettings.json”文件中的1个指定缓存项(键/值对)在内存或指定分布式软件中…

mysql-installer-community-8.0.22.0安装教程

1. 下载 mysql-installer-community-8.0.22.0安装包 首先去官网&#xff1a;https://dev.mysql.com/downloads/installer/ 下载MySQL。 2. 默认Next 3. 点击Execute 4. 同意安装 5. 点击Next后点Yes 6. 点击Execute 这里出现10个选项是正确的&#xff0c;如果不是&#xff0c;…

C++ vector 容器介绍

C vector 容器介绍 C的vector是标准库中常见的一种容器&#xff0c;使用起来非常方便&#xff0c;可以用来代替c原本的数组。vector是种容器&#xff0c;类似数组一样&#xff0c;但它的size可以动态改变。vector的元素在内存中连续排列&#xff0c;这一点跟数组一样。由于vect…

队列同步器AQS的实现与分析——独占锁模式

AQS独占锁模式源码分析1、tryAcquire()、acquire()方法2、addWaiter()方法3、acquireQueued()方法4、shouldParkAfterFailedAcquire()方法5、tryRelease()、release()方法1、tryAcquire()、acquire()方法 protected boolean tryAcquire(int arg) {throw new UnsupportedOperat…

glassfish任意文件读取漏洞

glassfish任意文件读取漏洞1.简介1.1.漏洞类型1.2.漏洞成因1.3.语法搜索1.4.影响版本2.漏洞复现2.1.POC2.2.访问地址2.3.GlassFish的敏感目录2.3.1.获取数据库密码2.3.2.获取GlassFish的后台密码2.4.POC脚本1.简介 GlassFish是一款强健的商业兼容应用服务器&#xff0c;达到产品…

MyEclipse提示过期,MyEclipse Subscription Expired激活方案

一、错误描述 紧接上文&#xff0c;虽然解决了MyEclipse提示过期问题&#xff0c;但是你会发现出现一行红色提示如下&#xff1a; 1.错误日志 Product activation must be completed within 5 days. 2.错误说明 产品激活必须在5天内完成。 二、解决方案 从错误日志很明显的可…