JAVA模拟堆

news2024/11/18 17:23:35

堆的性质

堆是一种特殊的树。

只要满足以下两点,它就是一个堆:

  • 堆是一个完全二叉树。
  • 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值。

第一点,堆必须是一个完全二叉树。完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列,自然堆也具有完全二叉树的所有性质。

第二点,堆中的每个节点的值必须大于等于(或者小于等于)其子树中每个节点的值。实际上,我们还可以换一种说法,堆中每个节点的值都大于等于(或者小于等于)其左右子节点的值。这两种表述是等价的。

对于每个节点的值都大于等于子树中每个节点值的堆,我们叫做“大顶堆”。对于每个节点的值都小于等于子树中每个节点值的堆,我们叫做“小顶堆”。

以下讲解都用小根堆为例。

堆的相关参数定义

static int[] h;   //存放堆中的数据
static int[] ph;  //存放第k个插入点的下标
static int[] hp;  //存放堆中点的插入次序
static int size;  //存放堆中数据个数

堆虽然是一种树,但在堆的存储中,通常使用数组存储。这是因为数组在从下标1开始存储值的时候,假设树根root为n,那么它的左子树为2n,右子树为2n+1。

堆的平衡

堆是个树状存储结构,在你对堆中的数据做出修改时,可能会破坏平衡,所以需要对堆做出操作让其重新平衡。

下沉

 //顾名思义,down()就是把当前节点在树中从上往下沉
    public static void down(int u) {
        //比较当前节点和其左右节点,找出最小的节点与当前节点交换
        int t = u;
        if (u * 2 <= size && h[t] > h[u * 2]) t = u * 2;
        if (u * 2 + 1 <= size && h[t] > h[u * 2 + 1]) t = u * 2 + 1;
        //如果当前节点已经是最小的,说明当前节点已经在合适的位置
        if (u != t) {
            heapSwap(u, t);
            down(t);
        }
    }

上浮

//将当前节点往上浮
    public static void up(int u) {
        //比较当前节点和其父节点的大小,并交换
        if (u / 2 > 0 && h[u] < h[u / 2]) {
            heapSwap(u, u / 2);
            up(u / 2);
        }
    }

而堆中最核心的操作也是下沉和上浮,基本上有了这两个方法,所有操作都没什么问题了。

其他方法

在这里因为需要维护堆中插入数据的顺序,所以这里需要一个额外的swap

	 /**
     * 此方法保证了可以找到第k个插入的数
     * 之所以要进行这样的操作是因为 经过一系列操作 堆中的元素并不会保持原有的插入顺序
     * 从而我们需要对应到原先第K个堆中元素
     * 如果理解这个原理 那么就能明白其实三步交换的顺序是可以互换
     * h,hp,ph之间两两存在映射关系 所以交换顺序的不同对结果并不会产生影响
     *
     * @param u
     * @param v
     */
    public static void heapSwap(int u, int v) {
        swap(h, u, v);
        swap(hp, u, v);
        swap(ph, hp[u], hp[v]);
    }

    public static void swap(int[] a, int u, int v) {
        int tmp = a[u];
        a[u] = a[v];
        a[v] = tmp;
    }

堆的插入

				//在堆中插入元素x
                int x = sc.nextInt();
                m++;
                h[++size] = x;
                ph[m] = size;
                hp[size] = m;
                //插入操作默认是插入到最后面的节点,所以只需要up一次就可以达到平衡
                //down(size);
                up(size);

堆的删除

 //删除最小值,不能直接删除堆顶,需要将堆底的元素与堆顶交换,然后删除堆底(也就是最小值),因为是堆顶,所以只需要down,恢复平衡
                heapSwap(1, size);
                size--;
                down(1);
 注:如果想要删除任意节点,也需要把节点k与堆底节点交换,然后删除再平衡

堆的修改

				//修改第k个插入的数为x
                int k = sc.nextInt(), x = sc.nextInt();
                h[ph[k]]=x;                 //此处由于未涉及heapSwap操作且下面的up、down操作只会发生一个
                down(ph[k]);                //所以可直接传入ph[k]作为参数
                up(ph[k]);

完整代码

package Hello.Acwing;

import java.util.Scanner;

public class Heap {
    static int[] h;   //存放堆中的数据
    static int[] ph;  //存放第k个插入点的下标
    static int[] hp;  //存放堆中点的插入次序
    static int size;  //存放堆中数据个数

    //堆是个树状存储结构,在你对堆中的数据做出修改时,可能会破坏平衡,所以需要down()和up()
    //顾名思义,down()就是把当前节点在树中从上往下沉
    public static void down(int u) {
        //比较当前节点和其左右节点,找出最小的节点与当前节点交换
        int t = u;
        if (u * 2 <= size && h[t] > h[u * 2]) t = u * 2;
        if (u * 2 + 1 <= size && h[t] > h[u * 2 + 1]) t = u * 2 + 1;
        //如果当前节点已经是最小的,说明当前节点已经在合适的位置
        if (u != t) {
            heapSwap(u, t);
            down(t);
        }
    }

    //将当前节点往上浮
    public static void up(int u) {
        //比较当前节点和其父节点的大小,并交换
        if (u / 2 > 0 && h[u] < h[u / 2]) {
            heapSwap(u, u / 2);
            up(u / 2);
        }
    }

    /**
     * 此方法保证了可以找到第k个插入的数
     * 之所以要进行这样的操作是因为 经过一系列操作 堆中的元素并不会保持原有的插入顺序
     * 从而我们需要对应到原先第K个堆中元素
     * 如果理解这个原理 那么就能明白其实三步交换的顺序是可以互换
     * h,hp,ph之间两两存在映射关系 所以交换顺序的不同对结果并不会产生影响
     *
     * @param u
     * @param v
     */
    public static void heapSwap(int u, int v) {
        swap(h, u, v);
        swap(hp, u, v);
        swap(ph, hp[u], hp[v]);
    }

    public static void swap(int[] a, int u, int v) {
        int tmp = a[u];
        a[u] = a[v];
        a[v] = tmp;
    }

    //	I x,插入一个数 x;
	//	PM,输出当前集合中的最小值;
	//	DM,删除当前集合中的最小值(数据保证此时的最小值唯一);
	//	D k,删除第 k个插入的数;
	//	C k x,修改第 k个插入的数,将其变为 x;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //操作的次数
        int n = sc.nextInt();
        //初始化
        h = new int[n + 1];
        ph = new int[n + 1];
        hp = new int[n + 1];
        size = 0;
        //m用来记录插入的数的个数
        int m = 0;
        for (int i = 0; i < n; i++) {
            String s = sc.next();

            if (s.equals("I")) {
                //插入
                int x = sc.nextInt();
                m++;
                h[++size] = x;
                ph[m] = size;
                hp[size] = m;
                //插入操作默认是插入到最后面的节点,所以只需要up一次就可以达到平衡
                //down(size);
                up(size);
            } else if (s.equals("PM")) {
                //输出最小值
                //小根堆,堆顶就是最小的
                System.out.println(h[1]);
            } else if (s.equals("DM")) {
                //删除最小值,不能直接删除堆顶,需要将堆底的元素与堆顶交换,然后删除堆底(也就是最小值),因为是堆顶,所以只需要down,恢复平衡
                heapSwap(1, size);
                size--;
                down(1);
            } else if (s.equals("D")) {
                //删除第k个插入的数
                int k = sc.nextInt();
                int u=ph[k];                //这里一定要用u=ph[k]保存第k个插入点的下标
                heapSwap(u,size);          //因为在此处heapSwap操作后ph[k]的值已经发生
                size--;                    //如果在up,down操作中仍然使用ph[k]作为参数就会发生错误
                //鉴于堆的性质,up()和down()只会有一个执行
                up(u);
                down(u);
            } else if (s.equals("C")) {
                //修改第k个插入的数为x
                int k = sc.nextInt(), x = sc.nextInt();
                h[ph[k]]=x;                 //此处由于未涉及heapSwap操作且下面的up、down操作只会发生一个
                down(ph[k]);                //所以可直接传入ph[k]作为参数
                up(ph[k]);
            }
        }
    }

}

堆的建立

    for (int i = n / 2; i; i--){
        down(i);
    }
//	时间复杂度为O(n);

那么堆为什么从n/2开始down呢?
在这里插入图片描述

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

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

相关文章

O2OA (翱途) 平台 V8.0 发布新增数据台账能力

亲爱的小伙伴们&#xff0c;O2OA (翱途) 平台开发团队经过几个月的持续努力&#xff0c;实现功能的新增、优化以及问题的修复。2023 年度 V8.0 版本已正式发布。欢迎大家到 O2OA 的官网上下载进行体验&#xff0c;也希望大家在藕粉社区里多提宝贵建议。本篇我们先为大家介绍应用…

Android 输入系统

概述 Android 输入系统的工作原理概括来说&#xff0c;内核将原始事件写入到设备节点中&#xff0c;InputReader 不断地通过 EventHub 将原始事件取出来并翻译加工成 Android 输入事件&#xff0c;然后交给 InputDispatcher。 InputDispatcher 根据 WMS 提供的窗口信息将事件…

24.Stream流

Stream流 一、什么是Stream流 Stream流操作是Java 8提供一个重要新特性&#xff0c;它允许开发人员以声明性方式处理集合&#xff0c;其核心类库主要改进了对集合类的 API和新增Stream操作。Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中&…

2023全栈开发人员职业路线图

0. 全栈开发人员职业路线图 全栈开发人员是IT行业中薪资最高的职业之一。 如果您想成为一名全栈开发人员&#xff0c;以下是2023年全栈开发人员路线图上的十一个步骤&#xff1a; 掌握敏捷开发和Scrum学习浏览器技术&#xff0c;如HTML和CSS熟练掌握JavaScript或TypeScript了…

单月涨粉303.72w,反差感才是主流吗?

据新抖「直播带货风向」数据显示&#xff0c;抖音4月的直播商品数量达到1021.32w&#xff0c;较上月的695.91w环比增长50.18%&#xff0c;直播销量环比增加16.81%。从这几个数值就可以看出4月的抖音电商依旧如火如荼...... 那么&#xff0c;4月&#xff0c;抖音又出现哪些新的看…

“世界中医药之都” 亳州市医保局领导一行莅临万民健康交流指导

为进一步推进智慧医疗、智慧服务、智慧管理“三位一体”为主旨的“智慧中医、健康社区”项目建设。2023 年 5 月 3 日&#xff0c;“世界中医药之都” 亳州市医保局 局长 吴旭春 、 医保中心主任秦克靖 、 办公室主任徐伟 等一行 5 人莅临 万民健康交流 指导工作 &#xff0c…

day27_mysql

今日内容 零、 复习昨日 一、单表查询 二、多表联查 零、 复习昨日 1 DDL,DML,DQL是啥 DDL 数据定义语言&#xff08;库&#xff0c;表&#xff0c;列&#xff09;DML 数据操作语言&#xff08;表内数据的操作增删改&#xff09;DQL 数据查询语言&#xff08;表内数据的查询&am…

酷游浅谈网站Javas cript型别

最近整理了一下&#xff0c;【酷游娜娜手机&#x1d54d;找看看nay3989提供】就决定跟大家讨论一下最近对于Javascripet的型别认识。 弱型别&#xff36;&#xff33; 强型别 Javascripet是一种「弱型别」的语言&#xff0c;所以会产生很多你意想不到恶心的事情 至于什么是弱…

软件测试、测试和开发、测试和调试【详细介绍】

目录 一、什么是软件测试 1.软件测试的定义 2.软件测试的目的 3.软件测试的不可穷性 二、开发和测试的区别 三、测试和调试的区别 一、什么是软件测试 在日常生活中&#xff0c;测试是无处不在的。比如新买的手机是否好用、新买的衣服穿着是否合身等等场景&#xff0c;均…

点成案例丨细胞培养芯片用于构建肠模型实例分享

器官芯片是一种利用微芯片制造技术制造的微流体细胞培养设备。该设备包含多个连续灌注腔室&#xff0c;具有多细胞层结构、组织界面、物理化学微环境以及人体血管循环等特征&#xff0c;可以模拟和重构人体器官的生理功能&#xff0c;为相关研究提供了可靠的平台。器官芯片技术…

java中设计模式总结

设计模式是实际工作中写的各种代码进行高层次抽象的总结&#xff0c;其中最出名的当属 Gang of Four (GoF) 的分类了&#xff0c;他们将设计模式分类为 23 种经典的模式&#xff0c;根据用途又可以分为三大类&#xff0c;分别为创建型模式、结构型模式和行为型模式。 有一些重…

【6D位姿估计】Point Pair Feature (PPF)

论文链接:Drost et al. Model Globally, Match Locally: Efficient and Robust 3D Object Recognition. CVPR, 2010. Model Globally, Match Locally 论文名字用 4 个词高度总结了 PPF 算法的精髓:“整体建模,局部匹配”。 下面这张图反应了论文的基本思想(算法概要): …

【鸿蒙应用ArkTS开发系列】- 导航栏Tab组件使用讲解

目录 Tabs介绍Tabs使用例子TabBar 样式设置定义菜单样式对象-NavigationItem定义一个底部菜单栏集合数据-NavigationList修改TabBuilder Tab 组件控制题外话 现在市场上的大部分应用&#xff0c;主页都是才用底部导航栏菜单作为页面主体框架来展示&#xff0c; 在鸿蒙中是使用…

STM32库函数笔记分享

之前刚开始自学的部分STM32笔记放出&#xff0c;希望对新入门STM32和想要复习库函数的小伙伴们起到帮助。 建立工程 1.寄存器操作方式 需要不断地查手册来了解每一位是干什么用的 优点&#xff1a;代码简介&#xff1b; 缺点&#xff1a;不太方便。 2.库函数操作方式 1.调用库函…

【leetcode热题100】接雨水、直方图最大矩形面积、矩阵中最大的矩形

文章目录 一、接雨水方法一&#xff1a;按列求&#xff08;动态规划&#xff09;方法二&#xff1a;双指针方法三&#xff1a;单调栈 二、直方图最大矩形面积单调栈哨兵位优化 三、矩阵中最大的矩形前缀和单调栈 一、接雨水 题目链接 题目描述&#xff1a; 给定 n 个非负整数…

JVM_垃圾回收器

目录 一、GC分类1.串行vs并行2.并发式vs独占式3.压缩式vs非压缩式4.年轻代vs老年代 二、GC评估指标1.吞吐量2.暂停时间3.小结 三、垃圾回收器都有哪些&#xff1f;1.GC发展史2.7种GC组合关系&#xff1f;3.为什么这么多GC4.如何查看默认GC?5.Serial GC&#xff1a;串行回收5.1…

字符设备驱动开发实验

我们以 chadev 这个虚拟设备为 例&#xff0c;完整的编写一个字符设备驱动模块。chadev 不是实际存在的一个设备&#xff0c;是为了方 便讲解字符设备的开发而引入的一个虚拟设备设备有两个缓冲区&#xff0c;一个为读缓冲 区&#xff0c;一个为写缓冲区&#xff0c;这两个缓冲…

Spring事务隔离级别详解

Spring有五大隔离级别&#xff1a; 1、ISOLATION_DEFAULT 2、ISOLATION_READ_UNCOMMITTED 3、ISOLATION_READ_COMMITTED 4、ISOLATION_REPEATABLE_READ 5、ISOLATION_SERIALIZABLE ISOLATION_DEFAULT 用底层数据库的设置隔离级别。 ISOLATION_READ_UNCOMMITTED 一个事…

java 数组创建的方法

数组是一个由一组元素组成的集合&#xff0c;我们可以用一个数组来表示集合。 java中最基本的数据类型是字符串&#xff0c;其长度是固定的&#xff0c;且不可变&#xff0c;一个字符串只能以一个数字开头。 在 Java中我们可以通过 myConst关键字来指定数组的长度。下面就看一下…

直线飙升到10万+star的AutoGpt,有多强?帮我写了个网页!

先来感受一下10万的star&#xff0c;到底有多强&#xff01; 从4月2日开始&#xff0c;直线飙升到10万star Auto-GPT是一个实验性的开源应用程序&#xff0c;展示了GPT-4语言模型的功能。这个程序由GPT-4驱动&#xff0c;将LLM“思想”链接在一起&#xff0c;以自主实现您设定的…