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

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

文章目录

  • 数据结构 优先级队列(堆)
    • 1. 优先级队列
      • 1.1 概念
    • 2. 优先级队列的模拟实现
      • 2.1 堆的概念
      • 2.2 堆的存储方式
      • 2.3 堆的创建
        • 2.3.1 堆向下调整
        • 2.3.2 堆的创建
        • 2.3.3 建堆的时间复杂度
      • 2.4 堆的插入与删除
        • 2.4.1 堆的插入
        • 2.4.2 堆的删除
      • 2.5 用堆模拟实现优先级队列
    • 3. 常用接口介绍
      • 3.1 PriorityQueue的特性
      • 3.2 PriorityQueue常用接口介绍
        • **3.2.1 优先级队列的构造**
        • 3.2.2 插入/删除/获取优先级最高的元素
      • 3.3 OJ练习
    • 4. 堆的应用

1. 优先级队列

1.1 概念

在前面的文章里曾介绍过队列,队列是一种先进先出的数据结构,但有些情况下,我们需要操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列

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

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

JDK1.8中的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 堆的存储方式

从堆的概念可知,堆是一棵完全二叉树,因此可以采用顺序存储的方式来进行高效存储

在这里插入图片描述

注:对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低

将元素存储到数组中后,可以根据我们在二叉树文章中介绍到的性质5堆树进行还原。假设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} 中的数据,如果想要将它创建成堆又该怎么操作?

在这里插入图片描述

仔细观察上图后发现:根节点的左右子树已经完全满足堆的性质,因此在这里只需要将根节点向下调整好即可

向下调整(这里我们以小根堆为例):

  1. 使用parent标记需要标记的根节点,child标记parent的左孩子(parent如果有孩子一定是先有左孩子)
  2. 如果parent的左孩子存在,即:child < size进行以下操作,直到parent的左孩子不存在:
    • 判断parent右孩子是否存在,存在找到左右孩子中最小的孩子,让child进行标记
    • 将parent与较小的孩子child进行比较,如果:
      • parent小于较小的孩子child,调整结束
      • 否则:交换parent与较小的孩子child,交换完成之后,继续向下调整,即parent = child;child = parent * 2 + 1

在这里插入图片描述

代码示例:

public void shiftSmallDown(int parent,int len) {//左孩子int child = 2 * parent + 1;while(child < len) {//判断是否存在右孩子 且右孩子和左孩子哪个更小,小的为childif (child+1 < len &&  elem[child+1] < elem[child]) {child++;}//判断根节点和child的大小,若根节点小则结束遍历,否则交换两个节点if (elem[child] < elem[parent]) {swap(child, parent);parent = child;child = 2 * parent + 1;}else {break;}}}public void createSmallHeap() {for (int parent = (usderSize-1-1)/2;parent >= 0;parent--) {shiftSmallDown(parent, usderSize);}}public void swap(int x, int y) {int temp = elem[x];elem[x] = elem[y];elem[y] = temp;}

在这里插入图片描述

2.3.2 堆的创建

上述举的例子为特殊情况(即左右子树已经满足堆的特性,只需要修改根节点),那对于普通的序列{1, 5, 3, 8, 7, 6},即根节点的左右子树不满足堆的特性时,又该如何调整?(这里我们以大根堆为例)

在这里插入图片描述

在这里,我们找倒数第一个非叶子节点从该节点位置开始往前一直到根节点,每遇到一个节点,应用向下调整

代码示例:

public void shiftBigDown(int parent,int len) {//左孩子int child = 2 * parent + 1;while(child < len) {//判断是否存在右孩子 且右孩子和左孩子哪个更大,大的为childif (child+1 < len &&  elem[child+1] > elem[child]) {child++;}//判断根节点和child的大小,若根节点大则结束遍历,否则交换两个节点if (elem[child] > elem[parent]) {swap(child, parent);parent = child;child = 2 * parent + 1;}else {break;}}}
public void swap(int x, int y) {int temp = elem[x];elem[x] = elem[y];elem[y] = temp;}
public void createBigHeap() {//找倒数第一个非叶子节点,从该节点位置开始往前一直到根节点,每遇到一个节点,应用向下调整for (int parent = (usderSize-1-1)/2;parent >= 0;parent--) {shiftBigDown(parent, usderSize);}}

在这里插入图片描述

2.3.3 建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

在这里插入图片描述

因此,建堆的时间复杂度为O(N)

2.4 堆的插入与删除

2.4.1 堆的插入

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

  1. 先将元素放到底层空间中(注:空间不够时需要扩容)
  2. 将最后新插入的节点向上调整,直到满足堆的性质

在这里插入图片描述

代码示例:

public void push(int val) {// 满了则扩容if (isFull()) {elem = Arrays.copyOf(elem,2*elem.length);}elem[usderSize] = val;//向上调整shiftBigUp(usderSize);usderSize++;}private boolean isFull() {return usderSize == elem.length;}/*** 向上调整* 这里以大根堆为例*/public void shiftBigUp(int child) {int parent = (child - 1)/2;while(child > 0) {if (elem[parent] < elem[child]) {swap(child,parent);child = parent;parent = (child - 1) / 2;}else {break;}}}public void swap(int x, int y) {int temp = elem[x];elem[x] = elem[y];elem[y] = temp;}

在这里插入图片描述

2.4.2 堆的删除

堆删除的一定是堆顶元素,操作方法如下:

  1. 将堆顶元素与堆中最后一个元素交换
  2. 将堆中有效数据个数减少一个
  3. 对堆顶元素进行向下调整

在这里插入图片描述

代码示例:

 /*删除元素*/
public int pop() {if (empty()) {return -1;}int oldVal = elem[0];swap(0,usderSize-1);usderSize--;shiftBigDown(0,usderSize);return oldVal;}
public boolean empty() {return usderSize == 0;}public void shiftBigDown(int parent,int len) {//左孩子int child = 2 * parent + 1;while(child < len) {//判断是否存在右孩子 且右孩子和左孩子哪个更大,大的为childif (child+1 < len &&  elem[child+1] > elem[child]) {child++;}//判断根节点和child的大小,若根节点大则结束遍历,否则交换两个节点if (elem[child] > elem[parent]) {swap(child, parent);parent = child;child = 2 * parent + 1;}else {break;}}}

在这里插入图片描述

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

代码示例:

package demo2;public class MyPriorityQueue {public int[] arr = new int[100];public int size = 0;public void offer(int e) {arr[size] = e;shiftBigUp(size);size++;}public int poll() {int oldval = arr[0];swap(0,size-1);size--;shiftBigDown(0,size);return oldval;}public int peek() {return arr[0];}public void shiftBigUp(int child) {int parent = (child - 1)/2;while(child > 0) {if (arr[parent] < arr[child]) {swap(child,parent);child = parent;parent = (child - 1) / 2;}else {break;}}}public void shiftBigDown(int parent,int len) {//左孩子int child = 2 * parent + 1;while(child < len) {//判断是否存在右孩子 且右孩子和左孩子哪个更大,大的为childif (child+1 < len &&  arr[child+1] > arr[child]) {child++;}//判断根节点和child的大小,若根节点大则结束遍历,否则交换两个节点if (arr[child] > arr[parent]) {swap(child, parent);parent = child;child = 2 * parent + 1;}else {break;}}}public void swap(int x, int y) {int temp = arr[x];arr[x] = arr[y];arr[y] = temp;}public void display() {for (int i = 0;i < size;i++) {System.out.print(arr[i] + " ");}}
}

在这里插入图片描述

3. 常用接口介绍

3.1 PriorityQueue的特性

Java集合框架中提供了PriorityQueuePriorityBlockingQueue两种类型的优先级队列,本文主要介绍PriorityQueue

在这里插入图片描述

使用PriorityQueue时需要注意:

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

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

  3. 不能插入null对象,否则会抛出NullPointeException

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

  5. 插入和删除元素的时间复杂度为O(log₂N)

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

  7. PriorityQueue默认情况下是小根堆–即每次获取到的元素都是最小的元素

3.2 PriorityQueue常用接口介绍

3.2.1 优先级队列的构造
构造器功能介绍
PriorityQueue()创建一个空的优先级队列,默认容量是11
PriorityQueue(int initialCapacity)创建一个初始容量为initialCapacity的优先级队列,注意: initialCapacity不能小于1,否则会抛IllegalArgumentException异 常
PriorityQueue(Collection<? extends E> c)用一个集合来创建优先级队列
package demo3;import java.util.ArrayList;
import java.util.PriorityQueue;public class TestPriorityQueue {public static void main(String[] args) {// 创建一个空的优先级队列,底层默认容量是11PriorityQueue<Integer> q1 = new PriorityQueue<>();// 创建一个空的优先级队列,底层默认容量是initialCapacityPriorityQueue<Integer> q2 = new PriorityQueue<>(100);ArrayList<Integer> list = new ArrayList<>();list.add(1234);list.add(123);list.add(12);list.add(1);// 用ArrayList对象来构造一个优先级队列的对象//此时q3中已经包含了三个元素PriorityQueue<Integer> q3 = new PriorityQueue<>(list);System.out.println(q3);System.out.println("size = " + q3.size());System.out.println("队头元素为:" + q3.peek());}}

在这里插入图片描述

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

class IntCmp implements Comparator<Integer> {public int compare(Integer o1, Integer o2) {return o2 - o1;}
}//大根堆构建优先级队列PriorityQueue<Integer> q4 = new PriorityQueue<>(new IntCmp());q4.offer(4);q4.offer(3);q4.offer(2);q4.offer(1);q4.offer(5);System.out.println(q4);

在这里插入图片描述

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

3.2.2 插入/删除/获取优先级最高的元素
函数名功能介绍
boolean offer(E e)插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时 间复杂度 ,注意:空间不够时候会进行自动扩容
E peek()获取优先级最高的元素,如果优先级队列为空,返回null
E poll()移除优先级最高的元素并返回,如果优先级队列为空,返回null
int size()获取有效元素的个数
void clear()清空
boolean isEmpty()检测优先级队列是否为空,空返回true
package demo3;import java.util.PriorityQueue;public class TestPriorityQueue2 {public static void main(String[] args) {int[] arr = {4, 1, 9, 2, 8, 0, 7, 3, 6, 5};//如果知道元素个数建议直接给底层容量PriorityQueue<Integer> q = new PriorityQueue<>(arr.length); // 注:默认时为小根堆//插入元素for (int e:arr) {q.offer(e);}System.out.println(q);System.out.println(q.peek()); // 获取优先级最高的元素System.out.println(q.size()); // 打印优先级队列中有效元素的个数//弹出元素q.poll();System.out.println(q);q.offer(0); // 插入元素后优先级会发生改变System.out.println(q);//清空队列q.clear();if (q.isEmpty()) {System.out.println("优先级队列为空");}else {System.out.println("优先级队列不为空");}}
}

在这里插入图片描述

3.3 OJ练习

top-k问题求最大或者最小的前k个数据

代码示例:

import java.util.PriorityQueue;
class Solution {public int[] smallestK(int[] arr, int k) {int[] ret = new int[k];if (k == 0 || arr == null) {return ret;}PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);for (int e:arr) {q.offer(e);}for (int i = 0;i < k;i++) {ret[i] = q.poll();}return ret;}
}

在这里插入图片描述

4. 堆的应用

这里我们拓展一下堆排序

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

  1. 建堆:

    • 升序:建大堆
    • 降序:建小堆
  2. 利用堆删除思想来进行排序:

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

在这里插入图片描述

代码示例:

public void heapSort() {int end = usderSize - 1;while(end > 0) {swap(0,end);shiftBigDown(0,end-1);end--;}}public void shiftBigDown(int parent,int len) {//左孩子int child = 2 * parent + 1;while(child < len) {//判断是否存在右孩子 且右孩子和左孩子哪个更大,大的为childif (child+1 < len &&  elem[child+1] > elem[child]) {child++;}//判断根节点和child的大小,若根节点大则结束遍历,否则交换两个节点if (elem[child] > elem[parent]) {swap(child, parent);parent = child;child = 2 * parent + 1;}else {break;}}}

在这里插入图片描述

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

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

相关文章

再见Jenkins!你好,GitLink引擎,更强大的自动化部署工具!

文章目录 写在前面一、准备工作1.1 注册GitLink账号1.2 托管项目1.3 新建项目管理引擎流水线 二、开始构建流水线2.1 进入图形流水线编辑页2.2 添加git clone节点2.3 添加shell节点2.4 添加allure html节点2.5 添加新建GitLink疑修节点2.6 添加钉钉通知节点2.7 设置任务触发器2…

Python学习基础笔记七十五——Python调用其他程序

Python经常被用来开发自动化程序。自动化程序往往需要调用其他的程序。 例如&#xff0c;我们可以代码中调用wget程序&#xff0c;而不是自己开发下载的代码。 这就是我们经常做的&#xff0c;在我们的Python程序中调佣其它程序&#xff0c;帮我们实现功能。 Python中调用外部…

危险化工品出口注意事项及法规要求_箱讯科技

随着全球化工品市场的不断发展&#xff0c;危险化工品出口业务逐渐成为国际贸易的重要组成部分。然而&#xff0c;由于危险化工品具有潜在的危险性&#xff0c;出口过程中需严格遵守相关法规和注意事项&#xff0c;以确保运输安全和顺畅。本文将详细介绍危险化工品出口注意事项…

Linux | vim的入门手册

目录 前言 一、什么是vim 二、vim编辑器的模式 1、插入模式 &#xff08;1&#xff09;用vim打开文件 &#xff08;2&#xff09;进入插入模式 2、默认模式 &#xff08;1&#xff09;光标移动 &#xff08;2&#xff09;复制、粘贴与剪切操作 &#xff08;3&#x…

UE4中无法保存项目问题

系列文章目录 文章目录 系列文章目录前言一、解决方法 前言 取消&#xff1a;停止保存所有资产并返回编辑器。 重试&#xff1a;尝试再次保存资产。 继续&#xff1a;仅跳过保存该资产。 当我点击继续时&#xff0c;关闭项目&#xff0c;然后重新打开项目&#xff0c;发现之前…

广州虚拟动力携数字人全栈式产品,邀您来2023世界VR产业大会(南昌)一探虚实

2023年10月19-20日&#xff0c;由工业和信息化部、江西省人民政府联合主办&#xff0c;中国电子信息产业发展研究院、江西省工业和信息化厅、南昌市人民政府、虚拟现实产业联盟共同承办的2023世界VR产业大会将在南昌绿地国际博览中心盛大举办。 广州虚拟动力作为3D、AI虚拟人领…

React之受控组件和非受控组件以及高阶组件

一、受控组件 受控组件&#xff0c;简单来讲&#xff0c;就是受我们控制的组件&#xff0c;组件的状态全程响应外部数据 举个简单的例子&#xff1a; class TestComponent extends React.Component {constructor (props) {super(props);this.state { username: lindaidai }…

VSCode 调试 u-boot

文章目录 VSCode 调试 u-boot调试配置启动 u-boot 脚本调试界面重定向之后继续调试参考 VSCode 调试 u-boot 调试配置 参考 qemu基础篇——VSCode 配置 GDB 调试 要想调试 u-boot 只需要再添加一个 u-boot 的配置即可 {"version": "0.2.0","conf…

学习笔记|串口通信实战|简易串口控制器|sprintf函数|STC32G单片机视频开发教程(冲哥)|第二十一集(下):串口与PC通信

目录 3.串口通信实战实操简易的工作原理Tips:sprintf函数简介 总结课后练习 3.串口通信实战 做一个简易串口控制器。发送对应指令&#xff0c;让板子做相应的事情&#xff0c;或者传输数据&#xff08;文本模式下发送&#xff0c;不要选择HEX&#xff09;。 1.串口发送字符Ax\…

2020年亚太杯APMCM数学建模大赛B题美国总统的经济影响分析求解全过程文档及程序

2020年亚太杯APMCM数学建模大赛 B题 美国总统的经济影响分析 原题再现&#xff1a; 美国总统选举每四年举行一次。 2020年是美国总统大选年&#xff0c;共和党候选人唐纳德特朗普和民主党对手乔拜登竞选总统。 甲乙双方候选人在金融贸易&#xff0c;经济金融治理&#xff0c;…

python爬虫实战-京东商品数据

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 今天介绍一下如何用 Python 来批量获取京东商品信息&#xff01;&#xff01; 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 第三方库: requests >>> pip install requests 开发环境: python 3.8 py…

【iOS】——用单例类封装网络请求

文章目录 一、JSONModel1.JSONModel的简单介绍2.JSONModel的使用 二、单例类和Block传值 一、JSONModel 1.JSONModel的简单介绍 JSONModel一个第三方库&#xff0c;这个库用来将网络请求到的JSON格式的数据转化成Foundation框架下的Model类的属性&#xff0c;这样我们就可以直…

Elasticsearch基础篇(五):创建es索引并学习分析器、过滤器、分词器的作用和配置

创建es索引并学习分析器、过滤器、分词器的作用和配置 一、基础概念Elasticsearch与MySQL的类比1. ES与MySQL的结构类比图2. ES与MySQL的类比示意表格3. 索引中重要概念索引&#xff08;Index&#xff09;文档&#xff08;Document&#xff09;字段&#xff08;Field&#xff0…

2023.10.18 区别 对象 和 类对象

目录 对象 类对象 总结 对象 对象是类的实例化结果它是内存中的一块区域&#xff0c;包含了该类的属性和方法的具体值和实现对象具有唯一的标识、状态、行为通过创建类的实例&#xff0c;我们可以在程序中操作和处理具体的对象 简单实例 class Person {public int high 1…

车载开发学习——CAN总线

CAN总线又称为汽车总线&#xff0c;全程为“控制器局域网&#xff08;Controller Area Network&#xff09;”&#xff0c;即区域网络控制器&#xff0c;它将区域内的单一控制单元以某种形式连接在一起&#xff0c;形成一个系统。在这个系统内&#xff0c;大家以一种大家都认可…

Netty系列教程之NIO基础知识

近30集的孙哥视频课程&#xff0c;看完一集整理一集来的&#xff0c;内容有点多&#xff0c;请大家放心食用~ 1. 网络通讯的演变 1.1 多线程版网络通讯 在传统的开发模式中&#xff0c;客户端发起一个 HTTP 请求的过程就是建立一个 socket 通信的过程&#xff0c;服务端在建立…

VR全景图片如何拍摄制作,拍摄制作过程中要注意什么?

引言&#xff1a; VR全景图片就是通过专业的相机设备捕捉到的一个空间的高清图像&#xff0c;再经过专业工具进行拼合&#xff0c;呈现出一种环绕式的视觉效果。想象一下&#xff0c;当你站在一个完全真实的环境中&#xff0c;可以自由地转动视角&#xff0c;看到四周的景色&a…

高数定理集合啦

haha~ 最近在准备数学竞赛&#xff0c;好久没有发布笔记啦&#xff0c;今天就来一波高数里常用的定理吧&#xff0c;不全面的话后续会更新哒~ 费马定理&#xff1a;对于一个函数的局部极值点&#xff0c;如果导数存在&#xff0c;那么导数在该点处必须为零&#xff0c;即 f(x)0…

SQL数据库管理工具RazorSQL mac中文版特点与功能

RazorSQL mac是一款功能强大的SQL数据库管理工具&#xff0c;它支持多种数据库&#xff0c;包括MySQL、Oracle、Microsoft SQL Server、SQLite、PostgreSQL等。 RazorSQL mac 软件特点和功能 多种数据库支持&#xff1a;RazorSQL支持多种数据库&#xff0c;用户可以通过一个工…

基于R语言的Meta分析【全流程、不确定性分析】方法与Meta机器学习高级应用

查看原文>>>【案例教程】基于R语言的Meta分析【全流程、不确定性分析】方法与Meta机器学习高级应用 Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析…