[Collection与数据结构] PriorityQueue与堆

1. 优先级队列

1.1 概念

前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话.
在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。
在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。
在这里插入图片描述

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

PriorityQueue底层实现使用了这种数据结构,堆实际上就是在完全二叉树的基础上做了一些调整,使得里面的元素按照一定地规则排列,而堆的元素存储在一个数组中.

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 堆的性质

  • 堆是一颗完全二叉树
  • 堆中的某个结点的值总是不大于或者不小于父结点的值

2.3 堆的存储方式

从堆的概念可知,堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储.
实例如上图所示.
将元素存储到数组中后,可以根据二叉树章节的性质对树进行还原。假设i为节点在数组中的下标,则有:

  • 如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
  • 如果2 * i + 1 小于节点个数(前提),则节点i的左孩子下标为2 * i + 1,否则没有左孩子
  • 如果2 * i + 2 小于节点个数(前提),则节点i的右孩子下标为2 * i + 2,否则没有右孩子

2.4 堆的创建

2.4.1 向下调整创建堆

对于集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据,如果将其创建成堆呢?
首先将数组中的元素按照二叉树层序遍历的方法进行存放.
在这里插入图片描述
接下来,我们要做的就是对上面这颗二叉树进行向下调整.那么如何向下调整呢:我们以大根堆为例来说明.

  • 从堆的最后一棵子树开始比较父结点和子结点的大小关系.
  • 如果父结点的值小于两个子结点其中一个的值,则交换两个值,如果不是则停止向下调整.
  • 之后把父结点向上移动,再次向下调整,直到遇到父结点大于子结点或者调整到了根结点.
    下面画图来举例,我们给定一个任意完全二叉树:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    之后以此类推…
    为什么要从子树开始调整呢?因为首先要保证子树是大根堆,调整根层数更小的树的时候才会好调整.就像我们盖房子一样,先要把地基打牢,才可以盖得起高楼大厦.
    在这里插入图片描述
    接下来我们通过代码来创建一个大根堆:
public class MyPriorityQueue {public int usedSize;public int[] elem;//堆通过数组来实现public MyPriorityQueue(int[] elem) {this.elem = elem;usedSize += elem.length;}public void createBigHeap(){for (int parent = (usedSize-2)/2; parent >= 0 ; parent--) {//从最后一棵子树开始调整//之后根向上父结点向上走shiftDown(parent,usedSize-1);//向下调整//子结点永远是这棵树的最后一个结点}}/*** 向下调整* @param parent 父节点* @param end 结束位置*/private void shiftDown(int parent,int end){int child = 2*parent+1;//此时child是左孩子while (child <= end){if (child+1 < usedSize && elem[child] < elem[child+1]){//若右孩子存在,并且大于左孩子child++;//让child到右孩子这里}if (elem[parent] < elem[child]){swap(parent,child);parent = child;child = 2*child+1;}else {break;//如果父结点不大于子结点,说明已经调整完成,因为是从下往上调的}}}private void swap(int s1,int s2){int tmp = elem[s1];elem[s1] = elem[s2];elem[s2] = tmp;}
}

2.4.1 创建堆的时间复杂度推导

在这里插入图片描述
因此:建堆的时间复杂度为O(N).

2.5 堆的插入与删除

2.5.1 堆的插入

堆的插入分下面两个步骤:

  • 把结点插入树的最后一个位置
  • 把这个结点进行向上调整

那么,向上调整又如何调整呢?我们以大根堆为例

  • 把插入元素所在的结点与父节点进行比较.
  • 如果父结点小于插入结点,则交换两个结点,否则停止向上调整.
  • 如果成功交换,重复上述步骤,知道父结点大于子结点或者比较到根结点.
    下面我们通过代码来展示:
/*** 插入元素* @param val 要插入的值*/public void offer(int val){if (isFull()){this.elem = Arrays.copyOf(elem,elem.length*2);}elem[usedSize] = val;usedSize++;shiftUp(usedSize-1);//这里注意是usedSize-1,因为usedSize++过,现在需要//向上调整的元素是usedSize-1位置上的元素}/*** 向上调整* @param child 因为向上调整的终点都是根结点,所以传入child*/private void shiftUp(int child){int parent = (child - 1)/2;while (child > 0){if (elem[parent] < elem[child]){swap(elem[parent],elem[child]);child = parent;parent = (parent-1)/2;}else {break;}}}/*** 判断堆元素是否为满* @return*/private boolean isFull(){if (elem.length == usedSize){return true;}else {return false;}}

2.5.2 堆的删除

注意:在删除元素的时候,一定删除的是堆顶元素.

  • 将堆顶元素和最后一个元素进行交换
  • 删除最后一个元素
  • 将堆顶结点进行向下调整
    在这里插入图片描述
    下面通过代码来展示:
/*** 删除堆顶元素*/public void poll(){if (usedSize == 0){return;}swap(elem[0],elem[usedSize-1]);usedSize --;shiftDown(0,usedSize-1);}

3. PriorityQueue

3.1 PriorityQueue的性质

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

  1. 使用时必须导入PriorityQueue所在的包,即:
    import java.util.PriorityQueue;
  2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出ClassCastException异常
  3. 不能插入null对象,否则会抛出NullPointerException
  4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
  5. 插入和删除元素的时间复杂度为O(log2N)
  6. PriorityQueue底层使用了堆数据结构
  7. PriorityQueue默认情况下是小堆—即每次获取到的元素都是最小的元素.要想创建大根堆,必须传入比较器对象.

3.2 PriorityQueue的使用

  1. 构造方法
构造器功能介绍
PriorityQueue()创建一个空的优先级队列,默认容量是11
PriorityQueue(int initialCapacity)创建一个初始容量为initialCapacity的优先级队列,注意:initialCapacity不能小于1,否则会抛IllegalArgumentException异常
PriorityQueue(Collection<? extends E> c)用一个集合来创建优先级队列
public PriorityQueue(Comparator<? super E> comparator)传入比较器----改变元素之间的比较规则

源码如下:

private static final int DEFAULT_INITIAL_CAPACITY = 11;//默认容量
public PriorityQueue() {this(DEFAULT_INITIAL_CAPACITY, null);}
public PriorityQueue(int initialCapacity) {//用户传入自定义容量this(initialCapacity, null);}
public PriorityQueue(Comparator<? super E> comparator) {this(DEFAULT_INITIAL_CAPACITY, comparator);//通过比较器改变优先级队列中元素的比较规则}
public PriorityQueue(Collection<? extends E> c) {if (c instanceof SortedSet<?>) {SortedSet<? extends E> ss = (SortedSet<? extends E>) c;this.comparator = (Comparator<? super E>) ss.comparator();initElementsFromCollection(ss);}else if (c instanceof PriorityQueue<?>) {PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;this.comparator = (Comparator<? super E>) pq.comparator();initFromPriorityQueue(pq);}else {this.comparator = null;initFromCollection(c);}}

使用实例:

static void TestPriorityQueue(){// 创建一个空的优先级队列,底层默认容量是11PriorityQueue<Integer> q1 = new PriorityQueue<>();// 创建一个空的优先级队列,底层的容量为initialCapacityPriorityQueue<Integer> q2 = new PriorityQueue<>(100);ArrayList<Integer> list = new ArrayList<>();list.add(4);list.add(3);list.add(2);list.add(1);// 用ArrayList对象来构造一个优先级队列的对象// q3中已经包含了三个元素PriorityQueue<Integer> q3 = new PriorityQueue<>(list);System.out.println(q3.size());System.out.println(q3.peek());}

通过传入比较器来创建大根堆:

public class Compare implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2-o1;}
}
public class Main {public static void main(String[] args) {PriorityQueue<Integer> priorityQueue1 = new PriorityQueue<>(new Compare());priorityQueue1.offer(1);priorityQueue1.offer(2);priorityQueue1.offer(3);priorityQueue1.offer(4);priorityQueue1.offer(5);System.out.println(priorityQueue1);//通过传入比较器对象,可以构建大根堆}
}

也可以通过传入比较器使得不可比较的对象变为可比较的对象:

public class Student{public String name;public int age;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}public Student(String name, int age) {this.name = name;this.age = age;}
}
import java.util.Comparator;/*** 年龄比较规则*/
public class Compare1 implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o2.age - o1.age;}
}
public class Main {public static void main(String[] args) {PriorityQueue<Student> priorityQueue2 = new PriorityQueue<>(new Compare1());//由于Student没有默认创建比较方法,所以必须传入比较器对象,否者异常priorityQueue2.offer(new Student("zhangsan",12));priorityQueue2.offer(new Student("lisi",17));priorityQueue2.offer(new Student("wangwu",19));System.out.println(priorityQueue2);//按照年龄进行大根堆构建}
}

也可以不传入比较器,在类中重写Comparable接口的compareTo方法:

public class Student implements Comparable<Student>{public String name;public int age;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic int compareTo(Student o) {return this.name.compareTo(o.name);}
}
public class Main {public static void main(String[] args) {PriorityQueue<Student> priorityQueue3 = new PriorityQueue<>();priorityQueue3.offer(new Student("zhang",12));priorityQueue3.offer(new Student("li",15));priorityQueue3.offer(new Student("wang",17));System.out.println(priorityQueue3);}
}
  1. 插入,删除,获取优先级队列中的方法
函数名功能介绍
boolean offer(E e)插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度 ,注意:空间不够时候会进行扩容
E peek()获取优先级最高的元素,如果优先级队列为空,返回null
E poll()移除优先级最高的元素并返回,如果优先级队列为空,返回null
int size()获取有效元素的个数
void clear()清空
boolean isEmpty()检测优先级队列是否为空,空返回true
  1. 优先级队列的扩容
    jdk17 的源码如下:
public boolean offer(E e) {if (e == null)throw new NullPointerException();modCount++;int i = size;if (i >= queue.length)grow(i + 1);//大于队列大小的时候,进行扩容siftUp(i, e);size = i + 1;return true;}
private void grow(int minCapacity) {int oldCapacity = queue.length;// Double size if small; else grow by 50%int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, /* minimum growth */oldCapacity < 64 ? oldCapacity + 2 : oldCapacity >> 1/* preferred growth */);queue = Arrays.copyOf(queue, newCapacity);}

优先级队列的扩容说明:

  • 容量小于64的时候,按照2倍扩容.
  • 容量大于64的时候,按照1.5倍扩容.

4. top-k问题

这种算法一般适用于数据比较大的情况下,比如要在1亿,甚至10亿数据中找出前k的数据.
OJ链接
在这里插入图片描述

class BigComparator implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2-o1;//通过比较器来创建大根堆}
}
public class Top_k {public int[] smallestK(int[] arr, int k) {if (k <= 0){;return new int[0];//如果k==0,返回空数组}PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new BigComparator());//先取前k个元素放入for (int i = 0; i < k; i++) {priorityQueue.offer(arr[i]);}//如果堆顶元素大于遍历到的元素,删除堆顶元素,让遍历到的元素进来for (int i = k; i < arr.length; i++) {if (priorityQueue.peek() > arr[i]){priorityQueue.poll();priorityQueue.offer(arr[i]);}}int[] array = new int[k];//使用数组取出前k个元素for (int i = 0; i < k; i++) {array[i] = priorityQueue.poll();}return array;}
}

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

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

相关文章

自动化机器学习流水线:基于Spring Boot与AI机器学习技术的融合探索

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

Python常用包介绍

数据处理 1.numpy&#xff08;数据处理和科学计算&#xff09; import numpy as np np.set_printoptions(precision2, suppressTrue) # 设置打印选项&#xff0c;保留两位小数&#xff0c;禁止科学计数法arr np.arange(1, 6) # 使用arange函数创建数组 print(arr)# 输出&…

深度学习下的视觉SLAM综述

作者&#xff1a;黄泽霞&#xff0c;邵春莉 来源&#xff1a;《机器人》 编辑&#xff1a;东岸因为一点人工一点智能 深度学习下的视觉SLAM综述到目前为止&#xff0c;深度学习与SLAM的结合已经在视觉里程计、场景识别与全局优化等各种任务中取得了显著的成果。同时&#xf…

Prompt Engineering,提示工程

什么是提示工程&#xff1f; 提示工程也叫【指令工程】。 Prompt发送给大模型的指令。比如[讲个笑话]、[用Python编个贪吃蛇游戏]、[给男/女朋友写情书]等看起来简单&#xff0c;但上手简单精通难 [Propmpt]是AGI时代的[编程语言][Propmpt]是AGI时代的[软件工程][提示工程]是…

线上申报开放时间!2024年阜阳市大数据企业培育认定申报条件、流程和材料

2024年阜阳市大数据企业培育认定申报条件、流程和材料&#xff0c;线上申报开放时间整理如下 一、2024年阜阳市大数据企业培育认定申报要求 &#xff08;一&#xff09;经营范围 申请认定的企业应当从事以下生产经营活动&#xff1a; 1.从事数据收集、存储、使用、加工、传输、…

使用linux,c++,创作一个简单的五子棋游戏

#include <iostream> #include <vector> #include <unordered_map> using namespace std; // 棋盘大小 const int BOARD_SIZE 15; // 棋子类型 enum ChessType { EMPTY, BLACK, WHITE }; // 棋盘类 class ChessBoard { private: vect…

CHiME-8多通道远场语音识别Baseline介绍

语音领域每年都有很多比赛&#xff0c;每个比赛都有自己的侧重点&#xff0c;其中CHiME系列比赛的侧重点就是多通道远场语音识别&#xff0c;与其他的语音识别比赛有所区别的是&#xff0c;CHiME提供分布式麦克风和麦克风阵列数据&#xff0c;这样可以选择合适的前端算法以降低…

创建Spring Boot项目

选择Maven Archetype,之后再Archetype选择webapp 两个都打勾 这是当前的打勾 这个是以后都默认勾上 打开对应的路径&#xff0c;用vscode打开settings.xml 加入国内源 阿里云 若没有此文件可上网查找 若jar包出现问题&#xff0c;可在repostitory文件内全删除 之后在Maven刷…

第12章 最佳的UI体验——Material Design实战

第12章 最佳的UI体验——Material Design实战 其实长久以来&#xff0c;大多数人都认为Android系统的UI并不算美观&#xff0c;至少没有iOS系统的美观。以至于很多IT公司在进行应用界面设计的时候&#xff0c;为了保证双平台的统一性&#xff0c;强制要求Android端的界面风格必…

HarmonyOS开发案例:【 自定义弹窗】

介绍 基于ArkTS的声明式开发范式实现了三种不同的弹窗&#xff0c;第一种直接使用公共组件&#xff0c;后两种使用CustomDialogController实现自定义弹窗&#xff0c;效果如图所示&#xff1a; 相关概念 [AlertDialog]&#xff1a;警告弹窗&#xff0c;可设置文本内容和响应回…

了解HTTP代理服务器:优势、分类及应用实践

在我们日常的网络使用中&#xff0c;我们经常听到HTTP代理服务器这个术语。那么&#xff0c;HTTP代理服务器到底是什么&#xff1f;它有什么优势和分类&#xff1f;又如何应用于实践中呢&#xff1f;让我们一起来了解一下。 HTTP代理服务器是一种位于客户端和服务器之间的中间…

图像处理基础知识

图像处理基础知识 图像 1、模拟图像 模拟图像&#xff0c;又称连续图像&#xff0c;是指在二维坐标系中连续变化的图像&#xff0c;即图像的像点是无限稠密的&#xff0c;同时具有灰度值&#xff08;即图像从暗到亮的变化值&#xff09;。 2、数字图像 数字图像&#xff0…

Python基础知识—运算符和if语句(二)

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》 《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 1.输入和输出函数1.1输出函数1.2输入函数 2.常见运算符2.1赋值运算符2.2比较运算符2.3逻辑运算符2.4and逻辑与2.5or逻辑或2.6not逻…

ceph介绍

一、前言 Ceph 是一个完全分布式的系统&#xff0c;它将数据分布在整个集群中的多个节点上&#xff0c;以实现高可用性和容错性&#xff0c;ceph支持对象存储、块存储、文件存储所以被称为统一存储&#xff0c;ceph的架构由以下组件组成:mon、mgr、osd、mds、cephfs、rgw&#…

深度学习Day-14:RNN实现心脏病预测

&#x1f368; 本文为&#xff1a;[&#x1f517;365天深度学习训练营] 中的学习记录博客 &#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制] 要求&#xff1a; 本地读取并加载数据&#xff1b;了解循环神经网络RNN的构建过程&#xff1b;测试集accuracy达到87%…

自己搭建的大疆无人机RTMP流媒体服务延迟太大

流程&#xff1a;无人机摄像头->图传->遥控器->流媒体服务器->取流播放&#xff0c;延迟有10秒来的&#xff0c;大家有没有什么好的方案。

C# 结合JavaScript实现手写板签名并上传到服务器

应用场景 我们最近开发了一款笔迹测试功能的程序&#xff08;测试版&#xff09;&#xff0c;用户在手写板上手写签名&#xff0c;提交后即可测试出被测试者的心理素质评价分析。类似功能的场景还比如&#xff0c;在银行柜台办理业务&#xff0c;期间可能需要您使用手写设备进…

2023最新!nginx安装配置保姆级教程

2023最新!nginx安装配置保姆级教程 这篇文章了参考了这位的教程:https://blog.csdn.net/qq_36838700/article/details/129971765 导航 文章目录 2023最新!nginx安装配置保姆级教程一、nginx下载二、编译安装nginx安装pcre安装openssl、zlib、gcc依赖安装nginx 二、拓展 一、n…

低空经济+飞行汽车:载人无人机技术详解

低空经济与飞行汽车是近年来备受关注的话题。随着科技的不断进步&#xff0c;尤其是无人机技术的快速发展&#xff0c;飞行汽车已经从科幻概念逐渐变为现实。以下是对低空经济与飞行汽车&#xff0c;特别是载人无人机技术的详解&#xff1a; 1. 低空经济&#xff1a; 定义&…

javaEE--多线程学习-进程调度

进程调度不明白&#xff1f;看这一篇文章就够了&#xff0c;逻辑衔接严密&#xff0c;文末附有关键面试题&#xff0c;一个海后的小故事让你瞬间明白这里面的弯弯绕绕&#xff01; 目录 1.什么是进程&#xff1f; 2.进程控制块&#xff08;PCB&#xff09; 2.1 一个PCB就是一…