数据结构-堆和PriorityQueue

1.堆(Heap)

1.1堆的概念

堆是一种非常重要的数据结构,通常被实现为一种特殊的完全二叉树

如果有一个关键码的集合K={k0,k1,k2,...,kn-1},把它所有的元素按照完全二叉树的顺序存储在一个一维数组中,如果满足ki<=k2i+1且ki<=k2i+2(i=0,1,2,3...),则称这个集合为小堆;如果满足ki>=k2i+1且ki>=k2i+2(i=0,1,2,3...),则称这个集合为大堆。

简单来说,根节点的值为最大的堆叫做最大堆或大根堆,根节点的值最小的堆叫做最小堆或小根堆

1.2堆的性质

1.完全二叉树的性质

除了最后一层外,每一层都被填满,最后一层的节点从左往后填充

2.堆序性质

最大堆(大根堆):

对于每个节点i,其左子节点2i+1和右子节点2i+2的值都小于或等于i的值

最小堆(小根堆):

对于每个节点i,其左子节点2i+1和右子节点2i+2的值都大于或等于i的值

1.3堆的存储方式

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

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

1.4堆的创建

给定一个集合{28,16,20,19,29,35,66,50,26,38},如何将其创建为堆呢?

观察发现:根节点的左右子树都已经满足堆的性质,因此只需要将根节点向下调整即可 

1.4.1堆的向下调整

1.用parent表示要调整的节点,child表示parent的左孩子(注意:堆是一颗完全二叉树,如果parent有孩子一定是先有左孩子

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

       parent的右孩子是否存在,如果存在,则找到左右孩子中最小的元素,让child表示这个元素。

       将parent与较小的孩子child进行比较,如果parent小于child,调整结束。

       如果parent大于child,将parent和child进行交换,原来parent中较大的元素向下调整可能会导         致子树不满足堆的性质,因此要继续向下调整,即parent=child,child=2*parent+1,继续进           行步骤2

代码编写:

    public void shiftDown(int[] array,int parent){int child=2*parent+1;int size=array.length;while(child<size){//如果右孩子存在,用child标记左右孩子中较小的值if(child+1<size&&array[child+1]<array[child]){child++;}if(array[parent]>array[child]){swap(array,parent,child);//继续向下调整,为了保证子树也满足堆的性质parent=child;child=2*parent+1;}else{break;}}}private void swap(int[] array,int a,int b){int tmp=array[a];array[a]=array[b];array[b]=tmp;}

 注意:再调整以parent为根的二叉树时,必须满足parent的左子树和右子树已经时堆了才可以进行向下调整。

1.4.2堆的创建(小根堆)

那么对于普通的序列,即根节点的左右子树不满足堆的性质,又该如何创建呢?

例如对普通序列{2,7,8,5,4,1}进行小根堆的创建

根据上面的堆的向下调整,我们的思路就是要将根的左右子树都满足小根堆的特点,我们可以从下向上,从最后一个非叶子节点出发,将其与他们的左右孩子进行比较,将最小的值与非叶子节点进行交换(堆的向下调整),再继续向上执行上述操作,直到操作的节点为根节点即可

代码编写:

     public void createSmallestHeap(int [] array){int root=(array.length-2)>>1;for(;root>=0;root--){shiftDown(array,root);}}

 

1.5堆的插入和删除

1.5.1堆的插入

堆的插入总共需要2步:

1.先将元素放入底层(空间不够时,需要进行扩容)

2.将新插入的元素不断向上调整,直到满足堆的性质

观察可以发现:如果新插入的节点的父节点大于新插入的节点,就进行元素的交换,不断重复该动作

代码编写:

    //child表示新插入元素的索引public void shiftUp(int child){//找到新插入节点的父节点int parent=(child-1)/2;while(child>0){if(array[parent]>array[child]){swap(array,parent,child);child=parent;parent=(child-1)/2;}else {break;}}}
1.5.2堆的删除

堆在删除过程中需要注意删除的元素一定是堆顶元素

1.将堆顶元素和堆中的左后一个元素交换位置

2.将堆中有效个数减少一个

3.对堆顶元素进行向下调整

代码编写:

     public void shiftDown(int[] array,int parent){int child=2*parent+1;int size=array.length;while(child<size){//如果右孩子存在,用child标记左右孩子中较小的值if(child+1<size&&array[child+1]<array[child]){child++;}if(array[parent]>array[child]){swap(array,parent,child);//继续向下调整,为了保证子树也满足堆的性质parent=child;child=2*parent+1;}else{break;}}}public void delete(int[] array){swap(array,0,size-1);//size表示有效元素的个数size--;shiftDown(array,0);}

 

1.6堆的应用

1.堆排序(Heap Sort)

利用堆的性质对数组进行排序,时间复杂度为O(nlogn)

2.优先级队列(PriorityQueue)

堆是实现优先级队列的高校数据结构,支持快速的插入和删除操作

3.Dijkstra算法

在最短路径算法中,堆用于高效地选择当前距离最小的节点

4.Kth Largest Element

也叫topK问题,使用堆可以高效地找到数组中的第k大元素

2.PriorityQueue

2.1什么是优先级队列

通过之前的介绍,队列是一种先进先出(FIFO)的数据结构,但是优先情况下,操作的数据可能带有优先级,并不希望按照队列原始的顺序进行出栈,可能优先级高的元素想先出队列

在生活中有一个很常见的例子:当你在用听歌的时候,突然接到电话,音乐会自动停止,而执行通话的操作

优先级队列(PriorityQueue)是一种特殊的队列,其中的每个元素都有一个优先级,队列会根据优先级来决定元素的出队顺序,优先级高的元素先出队,优先级低的元素后出队,如果两个元素的优先级相同,则按照它们入队列的顺序出队

优先级队列通常基于堆这种数据结构实现,因为堆可以高效地进行插入和删除操作,同时保持元素的优先级顺序

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,我们主要进行讲解PriorityQueue

2.2PriorityQueue常见操作

PriorityQueue的常见方法:

方法解释
PriorityQueue()创建一个空的优先级队列,默认容量是11
PriorityQueue(int initialCapacity)创建一个初始容量为initialCapacity的优先级队列,注意:initialCapacity不能小于1,否则会抛出IllegalArgumentException异常
PriorityQueue(Collection<? extends E> c)用一个集合来创建优先级队列
boolean offer(E e)插入元素e,插入成功返回true,如果e对象为空,则会抛出NullPointerException异常,空间不够的时候会进行扩容
E peek()获取优先级最高的元素,如果优先级队列为空,返回null
E poll()移除优先级最高的元素,如果优先级队列为空,返回null
int size()获取有效元素的个数
void clear()清空队列
boolean isEmpty()检测优先级队列是否为空,如果为空返回true

 我们以一个复杂的类型来演示:

public class Student implements Comparable<Student>{private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Student o) {return this.age-o.age;}
}public class Test {public static void main(String[] args) {PriorityQueue<Student> queue=new PriorityQueue<>();Student s1=new Student("hajimi",21);Student s2=new Student("king",18);queue.offer(s1);queue.offer(s2);System.out.println(queue.size());for(Student s:queue){System.out.println(s);}queue.clear();System.out.println(queue.size());}
}

 

2.3优先级队列的特性

因为优先级队列是基于堆实现的,所以优先级队列的操作的时间复杂度其实就是堆在操作过程中的时间复杂度

1.插入:

将一个新元素插入到队列中,并根据优先级调整队列的顺序,时间复杂度为O(logn)

2.删除:

删除优先级最高的元素,时间复杂度为O(logn)

3.获取优先级最高的元素:

返回优先级最高的元素,但不删除它,时间复杂度为O(1)

4.更新优先级:

更新队列中某个元素的优先级,并调整队列的顺序,时间复杂度为O(logn)

5.Priority中放置的元素必须是能够比较大小的,不能插入无法比较大小的对象,否则会抛出ClassCastException异常

6.不能插入null,否则会抛出NullPointerException

7.PriorityQueue默认情况下是小堆

8.PriorityQueue其内部可以自动扩容

2.4PriorityQueue底层的扩容原理

PriorityQueue的 默认无参构造方法创建的数组长度为11

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

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

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

2.5实现一个优先级队列

package datastructure;import java.util.Arrays;public class PriorityQueue {private int elem[];//队列中有效元素的个数private int usedSize;private final static int DEFAULT_INIT_SIZE=11;public PriorityQueue(){elem=new int[DEFAULT_INIT_SIZE];}public PriorityQueue(int[] elem){this.elem=elem;this.usedSize=elem.length;int root=((elem.length-2)>>1);for(;root>=0;root--){shiftDown(root,elem.length);}}private void shiftDown(int parent,int len){int child=parent*2+1;while (child<len){if(child+1<len&&elem[child+1]<elem[child]){child++;}if(elem[parent]>elem[child]){swap(elem,parent,child);}else {break;}}}public boolean offer(int value){if(usedSize==elem.length){Arrays.copyOf(elem,2*elem.length);}elem[usedSize]=value;usedSize++;shiftUp(usedSize-1);return true;}private void swap(int[] elem,int a,int b){int tmp=elem[a];elem[a]=elem[b];elem[b]=tmp;}private void shiftUp(int child){int parent=(child-1)/2;while (child>0){if(elem[child]<elem[parent]){swap(elem,parent,child);child=parent;parent=(child-1)/2;}else {break;}}}public int size(){return usedSize;}public int peek(){if(usedSize==0){System.out.println("优先级队列中没有元素,无法获取元素");return -1;}return elem[0];}public boolean isEmpty(){return usedSize==0;}public int poll(){if(usedSize==0){System.out.println("优先级队列中没有元素,无法删除元素");return -1;}int value=elem[0];swap(elem,0,usedSize-1);usedSize--;shiftDown(0,usedSize-1);return value;}
}

对编写的代码进行运行测试:

public class Test {public static void main(String[] args) {PriorityQueue queue=new PriorityQueue();queue.offer(2);queue.offer(4);queue.offer(3);queue.offer(8);queue.offer(7);queue.offer(5);queue.offer(1);System.out.println(queue.peek());int a= queue.poll();System.out.println(a);System.out.println(queue.peek());System.out.println(queue.size());}
}

 

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

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

相关文章

【C#】Process、ProcessStartInfo启动外部exe

在C#中使用 Process 和 ProcessStartInfo 类启动外部 .exe 文件&#xff0c;可以按照以下步骤进行&#xff1a; 创建 ProcessStartInfo 实例&#xff1a;配置进程启动信息&#xff0c;包括可执行文件的路径、传递给该程序的参数等。 设置启动选项&#xff1a;根据需要配置 Pro…

oracle 基础语法复习记录

Oracle SQL基础 因工作需要sql能力&#xff0c;需要重新把sql这块知识重新盘活&#xff0c;特此记录学习过程。 希望有新的发现。加油&#xff01;20250205 学习范围 学习SQL基础语法 掌握SELECT、INSERT、UPDATE、DELETE等基本操作。 熟悉WHERE、GROUP BY、ORDER BY、HAVIN…

【Rust自学】20.2. 最后的项目:多线程Web服务器

说句题外话&#xff0c;这篇文章非常要求Rust的各方面知识&#xff0c;最好看一下我的【Rust自学】专栏的所有内容。这篇文章也是整个专栏最长&#xff08;4762字&#xff09;的文章&#xff0c;需要多次阅读消化&#xff0c;最好点个收藏&#xff0c;免得刷不到了。 喜欢的话…

RabbitMQ深度探索:简单实现 MQ

基于多线程队列实现 MQ &#xff1a; 实现类&#xff1a; public class ThreadMQ {private static LinkedBlockingDeque<JSONObject> broker new LinkedBlockingDeque<JSONObject>();public static void main(String[] args) {//创建生产者线程Thread producer n…

自定义多功能输入对话框:基于 Qt 打造灵活交互界面

一、引言 在使用 Qt 进行应用程序开发时&#xff0c;我们经常需要与用户进行交互&#xff0c;获取他们输入的各种信息。QInputDialog 是 Qt 提供的一个便捷工具&#xff0c;可用于简单的输入场景&#xff0c;但当需求变得复杂&#xff0c;需要支持更多类型的输入控件&#xff0…

国产编辑器EverEdit - 工具栏说明

1 工具栏 1.1 应用场景 当用户想显示/隐藏界面的标签栏、工具栏、状态栏、主菜单等界面元素时&#xff0c;可以通过EverEdit的菜单选项进行设置。 1.2 使用方法 选择菜单查看 -> 工具栏&#xff0c;在工具栏的子菜单中选择勾选或去掉勾选对应的选项。 标签栏&#xff1…

ASP.NET Core 中使用依赖注入 (DI) 容器获取并执行自定义服务

目录 一、ASP.NET Core 中使用依赖注入 (DI) 容器获取并执行自定义服务 1. app.Services 2. GetRequiredService() 3. Init() 二、应用场景 三、依赖注入使用拓展 1、使用场景 2、使用步骤 1. 定义服务接口和实现类 2. 注册服务到依赖注入容器 3. 使用依赖注入获取并…

虚幻UE5手机安卓Android Studio开发设置2025

一、下载Android Studio历史版本 步骤1&#xff1a;虚幻4.27、5.0、5.1、5.2官方要求Andrd Studio 4.0版本&#xff1b; 5.3、5.4、5.5官方要求的版本为Android Studio Flamingo | 2022.2.1 Patch 2 May 24, 2023 虚幻官网查看对应Andrd Studiob下载版本&#xff1a; https:/…

当大模型遇上Spark:解锁大数据处理新姿势

大模型与 Spark&#xff1a;技术初印象 在当今数字化浪潮中&#xff0c;大模型和 Spark 无疑是备受瞩目的两大技术。它们各自在人工智能和大数据处理领域大放异彩&#xff0c;而当这两者相遇&#xff0c;又会碰撞出怎样的火花呢&#xff1f;让我们先来分别认识一下大模型和 Sp…

第 1 天:UE5 C++ 开发环境搭建,全流程指南

&#x1f3af; 目标&#xff1a;搭建 Unreal Engine 5&#xff08;UE5&#xff09;C 开发环境&#xff0c;配置 Visual Studio 并成功运行 C 代码&#xff01; 1️⃣ Unreal Engine 5 安装 &#x1f539; 下载与安装 Unreal Engine 5 步骤&#xff1a; 注册并安装 Epic Game…

芝法酱学习笔记(2.6)——flink-cdc监听mysql binlog并同步数据至elastic-search和更新redis缓存

一、需求背景 在有的项目中&#xff0c;尤其是进销存类的saas软件&#xff0c;一开始为了快速把产品做出来&#xff0c;并没有考虑缓存问题。而这类软件&#xff0c;有着复杂的业务逻辑。如果想在原先的代码中&#xff0c;添加redis缓存&#xff0c;改动面将非常大&#xff0c…

VLAN 基础 | 不同 VLAN 间通信实验

注&#xff1a;本文为 “ Vlan 间通信” 相关文章合辑。 英文引文&#xff0c;机翻未校。 图片清晰度限于原文图源状态。 未整理去重。 How to Establish Communications between VLANs? 如何在 VLAN 之间建立通信&#xff1f; Posted on November 20, 2015 by RouterSwi…

LINUX部署微服务项目步骤

项目简介技术栈 主体技术&#xff1a;SpringCloud&#xff0c;SpringBoot&#xff0c;VUE2&#xff0c; 中间件&#xff1a;RabbitMQ、Redis 创建用户 在linux服务器home下创建用户qshh&#xff0c;用于后续本项目需要的环境进行安装配置 #创建用户 useradd 用户名 #设置登录密…

bat脚本实现自动化漏洞挖掘

bat脚本 BAT脚本是一种批处理文件&#xff0c;可以在Windows操作系统中自动执行一系列命令。它们可以简化许多日常任务&#xff0c;如文件操作、系统配置等。 bat脚本执行命令 echo off#下面写要执行的命令 httpx 自动存活探测 echo off httpx.exe -l url.txt -o 0.txt nuc…

堆的实现——堆的应用(堆排序)

文章目录 1.堆的实现2.堆的应用--堆排序 大家在学堆的时候&#xff0c;需要有二叉树的基础知识&#xff0c;大家可以看我的二叉树文章&#xff1a;二叉树 1.堆的实现 如果有⼀个关键码的集合 K {k0 , k1 , k2 , …&#xff0c;kn−1 } &#xff0c;把它的所有元素按完全⼆叉树…

edu小程序挖掘严重支付逻辑漏洞

edu小程序挖掘严重支付逻辑漏洞 一、敏感信息泄露 打开购电小程序 这里需要输入姓名和学号&#xff0c;直接搜索引擎搜索即可得到&#xff0c;这就不用多说了&#xff0c;但是这里的手机号可以任意输入&#xff0c;只要用户没有绑定手机号这里我们输入自己的手机号抓包直接进…

EF Core 学习笔记(数据迁移、一对多)

程序集依赖&#xff1a;Nuget:Microsoft.EntityFrameworkCoreTools 【定义配置文件】 定义上下文配置文件&#xff0c;继承DbContext类 public class InfoManageProDbContext : DbContext{/// <summary>/// 业务系统/// </summary>public DbSet<BusinessSyste…

FRP通过公网IP实现内网穿透

FRP通过公网IP实现内网穿透 一、简介二、安装服务端1、下载2、安装FRP3、使用 systemd 命令管理 frps 服务4、设置 frps 开机自启动 三、安装客户端1、下载2、安装FRP3、使用 systemd 命令管理 frpc 服务4、设置 frpc 开机自启动 四、访问仪表盘 一、简介 frp 是一款高性能的反…

K8S学习笔记-------1.安装部署K8S集群环境

1.修改为root权限 #sudo su 2.修改主机名 #hostnamectl set-hostname k8s-master01 3.查看网络地址 sudo nano /etc/netplan/01-netcfg.yaml4.使网络配置修改生效 sudo netplan apply5.修改UUID&#xff08;某些虚拟机系统&#xff0c;需要设置才能生成UUID&#xff09;#…

go运算符

内置运算符 算术运算符关系运算符逻辑运算符位运算符赋值运算符 算术运算符 注意&#xff1a; &#xff08;自增&#xff09;和–&#xff08;自减&#xff09;在 Go 语言中是单独的语句&#xff0c;并不是运算符 package mainimport "fmt"func main() {fmt.Printl…