JAVA模拟堆

2023-11-02

堆的性质

堆是一种特殊的树。

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

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

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

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

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

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

堆的相关参数定义

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呢?
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JAVA模拟堆 的相关文章

随机推荐

  • 编译预处理

    声明 经过长时间的学习 对宏定义尤其是条件编译这块存在盲区 特此整理笔记 此次内容参考 c 程序设计教程 第三版 清华大学出版社 一 编译预处理 编译预处理是在编译源程序之前 由预处理器对源程序进行加工处理工作 所谓预处理器 是包含在编译器
  • 【VSCode】【msys2】VS Code + msys2配置Windows下C/C++开发环境

    VSCode msys2 VS Code msys2配置Windows下C C 开发环境 一 Msys2配置 1 下载msys2 网址 https www msys2 org 2 安装msys2 x86 64 xxxx exe 这里没什难度
  • 解决linux停在starting automount无法启动

    http www 361way com solve the linux cantconn problem 1741 html 公司的一台centos 6 0 X64系统测试机 把root密码给了好几个组的人用 天晓得他们在上面折腾什么 过了
  • linux屏幕触碰事件,touch事件中的touches、targetTouches和changedTouches详解

    touches 当前屏幕上所有触摸点的列表 targetTouches 当前对象上所有触摸点的列表 changedTouches 涉及当前 引发 事件的触摸点的列表 通过一个例子来区分一下触摸事件中的这三个属性 1 用一个手指接触屏幕 触发
  • 【Doxygen】Doxygen使用教程(个人总结)

    Doxygen Doxygen使用教程 个人总结 简介Doxygen 引言 什么是Doxygen Doxygen 是一个程序的文件产生工具 可将程序中的特定批注转换成为说明文件 通常我们在写程序时 或多或少都会写上批注 但是对于其它人而言
  • Basic Level 1061 判断题 (15分)

    题目 判断题的评判很简单 本题就要求你写个简单的程序帮助老师判题并统计学生们判断题的得分 输入格式 输入在第一行给出两个不超过 100 的正整数 N 和 M 分别是学生人数和判断题数量 第二行给出 M 个不超过 5 的正整数 是每道题的满分
  • Open3D 非线性最小二乘拟合二维多项式曲线

    目录 一 算法原理 二 代码实现 三 结果展示 一 算法原理 多项式曲线表示为 p x p 1 x n p 2 x n
  • 基于Spring Cloud Alibaba 分布式微服务高并发数据平台化(中台)思想+多租户saas企业开发架构技术选型和设计方案

    基于Spring Cloud Alibaba 分布式微服务高并发数据平台化 中台 思想 多租户saas设计的企业开发架构 支持源码二次开发 支持其他业务系统集成 集中式应用权限管理 支持拓展其他任意子项目 架构源码可以加我WX haiwab
  • OpenAI/ChatGPT模型排行及推荐,收费&开源

    整理的免费及模型网站 openai chatgpt模型项目 openai chatgpt网站收集整理 LLM模型排行榜 随着大量的大型语言模型 LLM 和聊天机器人每周都在发布 通常都对其性能进行了浮夸的宣称 很难筛选出开源社区正在取得的真
  • java 中的静态变量,静态代码块,动态代码块,构造方法执行顺序的深入探究

    要想完全弄懂这个执行顺序 需要我们先了解几个概念 首先是类加载与对象的构造 类加载就是在第一次调用这个类的时候jvm虚拟机会通过类加载器在一个叫做方法区的逻辑内存中将所要用到的类的信息存放在里边 其中方法区有一个静态区 存放的是类中的静态
  • js触摸事件

    touch事件的绑定 电脑端的mouseDown mouseUp mouseMove分别对应移动端的touchstart touchend touchmove 下面的代码判断浏览器是电脑端还是移动端 如果是电脑端 就绑定鼠标事件 如果是移动
  • mybatis怎么忽略映射字段

    TableField exist false 表示该属性不为数据库表字段 但又是必须使用的 TableField exist true 表示该属性为数据库表字段 Mybatis Plus 插件有这个功能 可以看一下 TableName Ta
  • selenium+python切换浏览器窗口--详细讲解

    在浏览器页面打开窗口后 有时点击按钮会打开新的页面 我们需要切换到新的窗口才能去定位操作 不然无法操作 切换窗口代码如下 获取当前窗口信息及当前url current window driver current window handle
  • 如何优雅的备份MySQL数据?

    为什么要备份数据 先说一下为什么需要备份MySQL数据 一句话总结就是 为了保证数据的安全性 如果我们把数据只存储在一个地方 如果物理机器损坏 会导致数据丢失 无法恢复 还有就是我们每次手动修改线上数据之前 为了安全起见 都需要先备份数据
  • linux日志管理--rsyslog/journalctl/Logrotate

    本文针对centos系统 一 系统日志服务rsyslog 程序包 rsyslog 主程序 usr sbin rsyslogd CentOS 6 service rsyslog start stop restart status CentOS
  • java面向对象----抽象类 && 接口

    目录 抽象类与抽象方法 概念 抽象类应用 接 口 概念 接口的特点 接口应用举例 Java 8中关于接口的改进 内部类 如何声明局部内部类 局部内部类的特点 匿名内部类 总结 抽象类与抽象方法 概念 随着继承层次中一个个新子类的定义 类变得
  • Python 的画图函数 seaborn 简介

    seaborn 简介 seanborn 是 Python 的另外一个常用工具包 它基于 matplotlib 但画出的图形更加美观些 并且与 Pandas 的数据类型结合地较好 Import seaborn import seaborn a
  • win服务器系统授权,win服务器系统授权

    win服务器系统授权 内容精选 换一换 为了更加安全高效的使用云监控服务提供的主机监控功能 我们提供了最新方式的Agent授权方法 在安装主机监控Agent前 仅需要一键式单击该区域的授权按钮或者在创建弹性云服务器页面勾选云监控Agent委
  • 观测线程状态

    package com kuang Demo05 观察线程的状态 public class TestState public static void main String args Thread thread new Thread gt
  • JAVA模拟堆

    堆的性质 堆是一种特殊的树 只要满足以下两点 它就是一个堆 堆是一个完全二叉树 堆中每一个节点的值都必须大于等于 或小于等于 其子树中每个节点的值 第一点 堆必须是一个完全二叉树 完全二叉树要求 除了最后一层 其他层的节点个数都是满的 最后