Java中的Heap(堆)(如果想知道Java中有关堆的知识点,那么只看这一篇就足够了!)

        前言:(Heap)是一种特殊的完全二叉树,它在诸多算法中有着广泛的应用,本文将详细介绍Java中的堆。


✨✨✨这里是秋刀鱼不做梦的BLOG

✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客

先让我们看一下本文大致的讲解内容:

目录

1.堆的初识

        堆的定义

2.堆的存储方式 + 基本结论

        (1)堆的存储方式

        (2)堆中的基本结论

3.堆的创建

        (1)逐个插入元素

        (2)批量建堆

4.堆的基本操作

(1)插入操作

(2)删除操作

(3)返回堆顶元素

(4)判断堆是否为空


1.堆的初识

        ——堆是一种特殊的完全二叉树,分为最大堆(大顶堆)和最小堆(小顶堆)。最大堆的每个节点的值都大于或等于其子节点的值,最小堆的每个节点的值都小于或等于其子节点的值。

        堆常用于实现优先队列(PriorityQueue),在图算法(如Dijkstra最短路径算法和Prim最小生成树算法)中也有重要应用。(读者若有兴趣可以自行了解!

        堆的定义

        ——堆是一种特殊的完全二叉树,满足以下两个条件:

  1. 完全二叉树:

    1. 除了最后一层,其他层的节点都是满的,并且最后一层的节点从左到右连续排列。(如图)

  1. 堆性质:

    • 最大堆:每个节点的值都大于或等于其子节点的值。

    • 最小堆:每个节点的值都小于或等于其子节点的值。

        堆的这些性质使得堆顶元素(根节点)在最大堆中是最大值,在最小堆中是最小值。这样我们就大致的了解了什么是堆了!

2.堆的存储方式 + 基本结论

        (1)堆的存储方式

        从堆的概念可知,堆是一棵完全二叉树,通常情况下,堆是通过数组来实现,因为数组可以高效地访问任意位置的元素,并且通过简单的算术操作可以找到父节点和子节点的位置。(如左图a)

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

        ——这样我们就知道了堆就是将链式结构的完全二叉树转换为数组形式进行存储。

        (2)堆中的基本结论

        那么了解完了堆的基本存储形式,接下来让我们看看堆中的基本结论,从上文中我们已经提及在堆中我们可以通过简单的算术操作可以找到父节点和子节点的位置,那么如何实现呢?

现在我们假设 i 为节点在数组中的下标,则有:

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

       

        ——读者可以根据上图进行自我验证!!!

这样我们就大致的了解了堆中的基本结论了。

3.堆的创建

        ——创建堆的过程可以通过两种方式实现:逐个插入元素(使用向上调整算法)批量建堆(使用向下调整算法)。逐个插入元素的方法相对简单,但批量建堆的方法效率更高。

        (1)逐个插入元素

        这种方法通过逐个插入元素来创建堆,每次插入新元素后,使用向上调整算法操作将其移动到正确位置,以保持堆的性质。

import java.util.Arrays;public class MaxHeap {private int[] elem; // 存储堆元素的数组private int usedSize; // 堆中元素的数量// 构造函数,初始化堆的容量public MaxHeap(int maxSize) {this.elem = new int[maxSize];this.usedSize = 0;}// 逐个插入元素的方法public void offer(int val) {// 如果堆已满,扩展数组容量为原来的两倍if (isFull()) {elem = Arrays.copyOf(elem, 2 * elem.length);}// 将新元素放入数组的最后一位elem[usedSize++] = val;// 进行上浮操作,保持堆的性质shiftUp(usedSize - 1);}// 检查堆是否已满private boolean isFull() {return usedSize == elem.length;}// 上浮操作,将新插入的元素移动到正确位置private void shiftUp(int child) {int parent = (child - 1) / 2;// 当child不为根节点,并且父节点的值小于子节点的值时,进行交换while (parent >= 0) {if (elem[parent] < elem[child]) {swap(parent, child);child = parent;parent = (child - 1) / 2;} else {break;}}}// 交换数组中的两个元素private void swap(int fpos, int spos) {int tmp = elem[fpos];elem[fpos] = elem[spos];elem[spos] = tmp;}// 主函数测试public static void main(String[] args) {MaxHeap maxHeap = new MaxHeap(10);maxHeap.offer(3);maxHeap.offer(1);maxHeap.offer(6);maxHeap.offer(5);maxHeap.offer(2);maxHeap.offer(4);System.out.println("Heap array: " + Arrays.toString(maxHeap.elem));}
}

        其核心逻辑就是将一个一个数据插入到数组的最后,然后根据堆(最大堆 或 最小堆)的基本概念来创建一个堆。

        ——如上图插入一个22数据,然后根据向上调整算法来实现创建最大堆。

        (2)批量建堆

        批量建堆的方法首先将所有元素放入数组中,然后从最后一个非叶子节点开始进行向下调整算法的操作,将其调整到正确位置。

import java.util.Arrays;public class MaxHeap {private int[] elem; // 存储堆元素的数组private int usedSize; // 堆中元素的数量// 构造函数,初始化堆的容量public MaxHeap(int maxSize) {this.elem = new int[maxSize];this.usedSize = 0;}// 批量建堆的方法public void createHeap(int[] array) {// 将数组的每个元素插入到堆中for (int i = 0; i < array.length; i++) {elem[i] = array[i];usedSize++;}// 从最后一个非叶节点开始进行向下调整算法// 计算最后一个非叶节点的索引for (int parent = (usedSize - 1 - 1) / 2; parent >= 0; parent--) {shiftDown(parent, usedSize);}}// 下沉操作,将根节点向下移动以维持堆的性质private void shiftDown(int root, int len) {int child = 2 * root + 1; // 计算左孩子的索引while (child < len) {// 如果右孩子存在且大于左孩子,则选择右孩子if (child + 1 < len && elem[child] < elem[child + 1]) {child++;}// 如果孩子节点大于根节点,则交换它们,并继续向下调整if (elem[child] > elem[root]) {swap(child, root);root = child; // 更新根节点的索引child = 2 * root + 1; // 计算新的左孩子索引} else {break; // 如果孩子节点不大于根节点,结束向下调整}}}// 交换数组中两个元素的位置private void swap(int pos1, int pos2) {int temp = elem[pos1]; // 临时保存第一个位置的元素elem[pos1] = elem[pos2]; // 将第二个位置的元素赋值到第一个位置elem[pos2] = temp; // 将临时保存的元素赋值到第二个位置}// 主函数用于测试public static void main(String[] args) {MaxHeap maxHeap = new MaxHeap(10);int[] array = {3, 1, 6, 5, 2, 4};maxHeap.createHeap(array);System.out.println("Heap array: " + Arrays.toString(maxHeap.elem));}
}

        其核心思路就是先将数据全部放入数组中,在从下往上的一个一个的建立 (最大堆 或 最小堆),直到整棵树变为 (最大堆 或 最小堆)

        这样我们就了解了堆的两种创建方式了!

4.堆的基本操作

        堆的基本操作包括插入、删除和取出堆定元素、判断堆是否为空等。现在让我们详细介绍这些操作的实现方法。

(1)插入操作

        插入操作其实就是我们在创建堆中的逐个插入元素的操作,这里再让我们回顾一下:

// 插入元素的方法public void offer(int val) {// 如果堆已满,扩展数组容量为原来的两倍if (isFull()) {elem = Arrays.copyOf(elem, 2 * elem.length);}// 将新元素放入数组的最后一位elem[usedSize++] = val;// 进行上浮操作,保持堆的性质shiftUp(usedSize - 1);}// 检查堆是否已满private boolean isFull() {return usedSize == elem.length;}// 向上调整算法,将新插入的元素移动到正确位置private void shiftUp(int child) {int parent = (child - 1) / 2;// 当child不为根节点,并且父节点的值小于子节点的值时,进行交换while (parent >= 0) {if (elem[parent] < elem[child]) {swap(parent, child);child = parent;parent = (child - 1) / 2;} else {break;}}}

(2)删除操作

        删除操作的核心思想为:将栈顶的元素和数组最后一个元素进行交换之后,删除最后一个元素,之后再对堆进行整理(整理为最小堆或最大堆)

public void poll() {// 将根节点(索引0)与堆的最后一个节点交换位置swap(0, usedSize - 1);// 移除堆的最后一个节点(原根节点),减少堆的大小usedSize--;// 从根节点开始进行下沉调整,恢复堆的性质shiftDown(0, usedSize);
}private void swap(int pos1, int pos2) {// 交换堆中两个指定位置的元素int temp = elem[pos1];elem[pos1] = elem[pos2];elem[pos2] = temp;
}private void shiftDown(int root, int len) {int child = 2 * root + 1; // 计算左孩子的索引while (child < len) {// 如果右孩子存在且大于左孩子,则选择右孩子if (child + 1 < len && elem[child] < elem[child + 1]) {child++;}// 如果选中的孩子节点大于当前根节点,则交换并继续下沉if (elem[child] > elem[root]) {swap(child, root);root = child; // 更新根节点为刚刚下沉的孩子节点child = 2 * root + 1; // 更新孩子节点的索引} else {break; // 当前根节点已经大于或等于所有孩子节点,结束下沉}}
}

(3)返回堆顶元素

        其核心思想为:将堆中的首元素返回

public boolean isEmpty() {// 检查堆是否为空// 如果堆的大小为0,则返回true,表示堆为空;否则返回falsereturn usedSize == 0;
}public int peekHeap() {// 查看堆顶元素if (isEmpty()) {// 如果堆为空,则抛出异常throw new NullElementException("优先队列中没有元素!!!");}// 返回堆顶元素(根节点)return elem[0];
}

(4)判断堆是否为空

          其核心思想为:数组中有没有元素

public boolean isEmpty() {// 如果 usedSize(堆的当前大小)等于0,说明堆中没有元素,返回 true。// 否则,返回 false,表示堆中至少有一个元素。return usedSize == 0;
}

        ——通过上面的讲解,我们就大致的了解了堆中的基本操作。


以上就是本篇文章的全部内容了~~~

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

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

相关文章

生成式人工智能落地校园与课堂的15个场景

生成式人工智能正在重塑教育行业&#xff0c;为传统教学模式带来了革命性的变化。随着AI的不断演进&#xff0c;更多令人兴奋的应用场景将逐一显现&#xff0c;为学生提供更加丰富和多元的学习体验。 尽管AI在教学中的应用越来越广泛&#xff0c;但教师们也不必担心会被完全替代…

MySQL(事务、索引)MyBatis

目录 事务 概述 四大特性&#xff08;ACID&#xff09; 索引 结构 语法 MyBatis 数据库连接池 lombok 基本操作--增删改查 根据主键删除 新增 更新 查询&#xff08;根据ID查询&#xff09; 查询&#xff08;条件查询&#xff09; XML映射文件 动态SQL 动态条…

[图解]《分析模式》漫谈16-“我用的”不能变成“我的”

1 00:00:00,720 --> 00:00:02,160 今天&#xff0c;我们来说一下 2 00:00:02,170 --> 00:00:04,850 “我用的”不能变成“我的” 3 00:00:04,860 --> 00:00:11,390 《分析模式》的前言 4 00:00:12,260 --> 00:00:13,410 有这么一句话 5 00:00:14,840 --> 0…

Windows安装go语言开发环境

一、下载安装包 安装包下载地址 下载完毕后双击进行安装。 查看是否安装成功&#xff1a; go version #查看go版本 go env #查看go环境变量正常显示则安装完成。 二、安装vscode 一般开发go语言项目使用vscode工具&#xff1a; 下载地址 下载完毕后双击进行安装。 三…

【鸿蒙学习笔记】UI・常用组件・Button・按钮组件・ButtonType

官方文档&#xff1a;按钮 (Button) 目录标题 ButtonType ButtonType 胶囊类型&#xff08;Capsule&#xff09;・圆形按钮&#xff08;Circle&#xff09;・普通按钮&#xff08;Normal&#xff09;・自定义 Column({ space: 10 }) {Text(Normal).fontSize(20).fontColor(Col…

【人工智能 | 机器学习 | 理论篇】模型评估与选择

文章目录 1. 经验误差与过拟合2. 模型评估方法2.1 模型评估概念2.2 留出法2.3 k 折交叉验证法2.4 自助法2.5 调参与最终模型 3. 性能度量3.1 均方误差3.2 错误率、精度3.3 查准率、查全率3.3 扩展3.4 ROC 与 AUC3.5 代价敏感错误率与代价曲线 4. 比较检验4.1 假设检验4.2 交叉验…

matlab中plot的一些用法

文章目录 一、基本用法二、绘制多个数据集三、设置线型、颜色四、添加标题和标签五、添加图例六、设置轴范围七、绘制网格八、 在同一图中绘制多个子图九、绘制带误差条的图十、绘制半对数图和对数图十一、绘制填充区域图十二、综合案例 一、基本用法 x 0:0.1:10; y sin(x);…

技术成神之路:设计模式(八)责任链模式

介绍 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为设计模式&#xff0c;它允许多个对象依次处理请求&#xff0c;避免请求的发送者和接收者之间的显式耦合。该模式通过将多个可能处理请求的对象连接成一条链&#xff0c;并沿着这条链传递请求…

Cadence23导入板框时的疑难杂症

1.为啥导入板框之后元器件找不到了呢&#xff1f; 因为导入板框的时候没有勾选 增加量 &#xff0c;导致导入的板框新建了一个文件&#xff1a; 2.导入板框之后3D显示还是不没有导入呀&#xff1f; 那是因为导入的板框还带有铜皮属性&#xff0c;需要change命令把其换为板框…

【算法】算法模板

算法模板 文章目录 算法模板简介数组字符串列表数学树图动态规划 简介 博主在LeetCode网站中学习算法的过程中使用到并总结的算法模板&#xff0c;在算法方面算是刚过初学者阶段&#xff0c;竞赛分数仅2000。 为了节省读者的宝贵时间&#xff0c;部分基础的算法与模板未列出。…

51单片机13(动态数码管实验)

一、数码管动态显示原理 1、动态显示是利用减少段选线&#xff0c;分开位选线&#xff0c;利用位选线不同时选择通断&#xff0c;改变段选数据来实现的。 &#xff08;1&#xff09;多位数码管依然可以进行静态的一个显示&#xff0c;那么在前面我们介绍静态数码管的时候&…

Nginx(详解以及如何使用)

目录 1. 什么是Nginx&#xff1f; 2. 为什么使用nginx? 3. 安装nginx 3.1 安装nginx的依赖插件 3.2 下载nginx 3.3 创建一个目录作为nginx的安装路径 3.4 解压 3.5 进入解压后的目录 3.6 指定nginx的安装路径 3.7 编译和安装nginx 3.8 启动nginx 3.9 访问nginx 4. ngin…

【python】Python中闭包的是什么,闭包原理分析与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

MongoDB教程(十四):MongoDB查询分析

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、查询分…

队列及其应用(用栈实现队列 力扣225)

队列概念 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出FIFO(First In First Out) 入队列&#xff1a;进行插入操作的一端称为队尾 出队列&#xff1a;进行删除操作的一端称为队头 队列的代码…

09.甜甜圈旋转加载动画 计数器

甜甜圈旋转加载动画 创建一个甜甜圈形状的旋转加载动画,可用于指示内容的加载。 为整个元素使用半透明的 border。排除一侧,它将作为甜甜圈的加载指示器。定义并使用合适的动画,使用 transform: rotate() 旋转元素。<body><div class="donut"></div&…

基于JAVA+SpringBoot+Vue+uniapp的微信小程序点餐平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 点餐小程序主要为小个…

java——类变量和类方法;代码块;内部类

一、类变量和类方法 1.1、类变量 1.1.1、类变量内存布局(静态变量放在哪里&#xff1f;) 1、JVM7及以前的近代变量放在方法区中&#xff1b;JVM8以后的静态变量放在堆中 2、不管static变量在哪里&#xff0c;共识&#xff1a; 1&#xff09;Static变量是同一个类所有对象共…

昇思25天学习打卡营第17天 | 基于MindSpore实现BERT对话情绪识别

昇思25天学习打卡营第17天 | 基于MindSpore实现BERT对话情绪识别 文章目录 昇思25天学习打卡营第17天 | 基于MindSpore实现BERT对话情绪识别BERT模型对话情绪识别BERT模型的文本情绪分类任务数据集数据下载数据加载与预处理 模型构建模型验证模型推理 总结打卡 BERT模型 BERT&…

【Espressif-ESP32S3】【VScode】安装【ESP-IDF】插件及相关工具链

一、ESP-IDF简介 二、VScode安装ESP-IDF插件 三、安装ESP-IDF、ESP-IDF-Tools以及相关工具链 四、测试例程&编译烧录 五、IDF常用指令 资料下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/15Q2rl2jpIaKfj5rATkYE6g?pwdGLNG 提取码&#xff1a;GLNG 一、ESP-…