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

news2024/9/19 15:15:06

        前言:(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,表示堆为空;否则返回false
    return 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.coloradmin.cn/o/1937751.html

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

相关文章

《Milvus Cloud向量数据库指南》——开源许可证的开放度:塑造AI开发合作与创新的双刃剑

在人工智能(AI)技术日新月异的今天,开源软件作为推动技术创新的重要力量,其许可证的开放度成为了影响AI开发合作、创新模式乃至整个行业生态的关键因素。不同的开源许可证模型,以其各自独特的开放程度,不仅决定了软件项目的可访问性和可定制性,还深刻影响着AI领域内的合…

springboot 实体类加注解校验入参数据

导入的是springboot自身的依赖包 import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.Valid;

R语言进行K折交叉验证问题

在使用R语言进行模型参数评估优化时候&#xff0c;会使用K折交叉验证&#xff0c;其中会遇到各种各样问题&#xff1a; 错误: C5.0 models require a factor outcome > (1-mean(E0));(1-mean(E1)) [1] 1 [1] 1 报错说明C5.0模型需要因子变量输出&#xff0c;源代码如下&am…

还在为电脑录屏困扰吗?试试这4个方法,瞬间解决

现在很多人用手机进行日常操作都知道录屏的功能怎么操作&#xff0c;但是电脑录屏怎么录呢&#xff1f;如果你需要使用电脑进行录屏操作的时候就可以看看这篇文章。 1.福晰录屏大师 这个工具是一个专业的录屏软件。可以控制录制的区域范围&#xff0c;也能控制音频来源&#…

鸿蒙仓颉语言【匹配match】

模式匹配match match特性是现代编程语言中常见的特性&#xff0c;它们在不同的编程语言中有类似的概念和语法&#xff0c;但在细节上可能有一些差异。它们都可以提高代码的灵活性和可重用性&#xff0c;但用法和语法可能会因编程语言而异。 仓颉的match 支持通过箭头函数直接…

python用selenium网页模拟时xpath无法定位元素解决方法2

有时我们在使用python selenium xpath时&#xff0c;无法定位元素&#xff0c;红字显示no such element。上一篇文章写了1种情况&#xff0c;是包含iframe的&#xff0c;详见https://blog.csdn.net/Sixth5/article/details/140342929。 本篇写第2种情况&#xff0c;就是xpath定…

怎样对 PostgreSQL 中的慢查询进行分析和优化?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 怎样对 PostgreSQL 中的慢查询进行分析和优化&#xff1f;一、理解慢查询的危害二、找出慢查询&#x…

恶补,先验分布,后验分布 ,似然估计

恶补&#xff0c;打一遍增加印象 先验分布后验分布&#xff0c;似然估计 声明&#xff1a;仅记录个人学习&#xff0c;并无其他用途。 先验分布 后验分布&#xff0c; 似然估计 隔壁小哥的故事&#xff1a; 隔壁小哥要去15公里外的一个公园里玩&#xff0c;小哥可以选择步行…

使用 Elasticsearch 和 OpenAI 为你的客户成功应用程序构建对话式搜索

作者&#xff1a;来自 Elastic Lionel Palacin 在此博客中&#xff0c;我们将探讨如何通过利用大型语言模型 (LLM) 和检索增强生成 (RAG) 等技术实施对话式搜索来增强你的客户成功应用程序。 你将了解对话式搜索在客户成功应用程序环境中的优势&#xff0c;以及如何使用 Elast…

复制配置,多个端口号一起开启。

选择 输入&#xff1a;-Dserver.port8082

安卓原生聊天面板开发(一)整体规划

系列文章 安卓原生聊天面板开发&#xff08;一&#xff09;整体规划 安卓原生聊天面板开发&#xff08;二&#xff09;emoji功能实现 安卓原生聊天面板开发&#xff08;三&#xff09;录音交互实现 安卓原生聊天面板开发&#xff08;四&#xff09;整体交互实现 背景 产品喝…

Android 小白菜鸟从入门到精通教程

前言 Android一词最早出现于法国作家利尔亚当&#xff08;Auguste Villiers de l’Isle-Adam&#xff09;在1886年发表的科幻小说《未来的夏娃》&#xff08;L’ve future&#xff09;中。他将外表像人的机器起名为Android。从初学者的角度出发&#xff0c;通过通俗易懂的语言…

记录通过SSH连接Linux(VM)过程

记录通过SSH连接Linux&#xff08;VM&#xff09;过程 打开VM的虚拟网络编辑器&#xff0c; 点击NAT 设置&#xff0c;记录一下子网和网关 进入虚拟机&#xff0c;输入&#xff1a;vim /etc/sysconfig/network-scripts/ifcfg-ens*; 网关改成和上面一样的网关&#xff0c;IP…

Zabbix介绍和架构

目录 一.Zabbix简介 1.为什么需要监控 2.需要监控什么 3.常见的监控工具 4.Zabbix使用场景及系统概述 5.Zabbix 架构 6.Zabbix工作流程 7.Zabbix 术语 二. 部署安装zabbix 三.zabbix 配置文件 一.Zabbix简介 1.为什么需要监控 运维行业有句话:“无监控、不运维”&am…

朋友圈运营分享干货

朋友圈运营技巧对于提升个人或品牌的曝光度、增强用户粘性以及实现营销目标至关重要。 本篇是一些清晰、实用的朋友圈运营技巧~ 目标定位 明确目标群体&#xff1a;了解目标用户的年龄、兴趣、需求等特征以便精准定位内容。 竞争对手分析&#xff1a;观察和分析竞争对手的运…

echarts图表

记一个简单的关于轴线和toolip图表配置 getEcharts(xAxisData, analysisData) {// console.log(analysisData,"数据");var chart this.$echarts.init(this.$refs.classEcharts)let option {tooltip: {trigger: axis,className: echarts-tooltip,backgroundColor:…

vue+element的table合并单元格(竖着合并行)及合计行添加并计算

1 效果: 代码分析: 1 表格头配置: 2 懒得写的:自己复制吧 <el-table:data"tableData"style"width: 98%":height"height"v-loading"isLoading"stripe"false" :span-method"objectSpanMethod"show-summary:summ…

Redis之Zset

目录 一.介绍 二.命令 三.编码方式 四.应用场景 Redis的学习专栏&#xff1a;http://t.csdnimg.cn/a8cvV 一.介绍 ZSET&#xff08;有序集合&#xff09;是 Redis 提供的一种数据结构&#xff0c;它与普通集合&#xff08;SET&#xff09;类似&#xff0c;不同之处在于每个…

【Node】node的fs模块介绍及使用(一)

文章目录 简言File systemPromise example 承诺示例Callback example 回调示例Synchronous example 同步示例Promises API 承诺APIClass: FileHandleEvent: close 关闭文件filehandle.appendFile(data[, options]) 添加数据filehandle.chmod(mode) 修改权限filehandle.chown(ui…