【Java数据结构】——第九节.向上建堆和向下建堆的区别

news2024/11/26 14:40:45

 作者简介:大家好,我是未央;

博客首页:未央.303

系列专栏:Java初阶数据结构

每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!!!

文章目录

前言

一、向上调整来建堆

二、向下调整来建堆

三、向上建堆和向下建堆的优劣比较

总结


前言

上节内容我们介绍了优先级队列的实现方法,即通过向上调整和向下调整算法实现创建大根堆或者小根堆。本节内容我们将比较这两种算法,找出其优劣;


我们先举例对于这样一个数组来建立一个小根堆;

 

一、向上调整来建堆

思想:

首先,我们把第一个数字看成堆,也就是4,当第二个数字插入进去的时候,进行向上调整算法,使其确保为小堆;(向上调整的算法在上篇博文已详细讲解过,不过多赘述

一个一个的插入数据的过程就是遍历数组,确保数组里每一个数进行向上调整算法;

其过程的图解:

代码实现:

????????????????具体时间

public class Test2 {
    private  int[] array;
    private  int usedSize; // 当前堆中的有效元素个数
    Test2() {
        array = new int[10]; // 通过构造方法给数组分配空间
    }
    // 向上调整保证是小根堆:时间复杂度是O(log以2为底的N)---log2N
    public  void shiftUp(int child) {
        int parent = (child - 1) / 2;
        while (parent >= 0) {
            if (this.array[child] < this.array[parent]) {
                int tmp = this.array[child];
                this.array[child] = array[parent];
                this.array[parent] = tmp;
                child = parent;
                parent = (child - 1) / 2;
            }
            else {
                break;
            }
        }
    }
 
    // 向堆中插入元素
    public  void offerHeap(int val) {
        if (this.usedSize >= this.array.length) {
            System.out.println("数组已满,插入失败!");
        }
        else {
            this.array[usedSize] = val;
            shiftUp(usedSize);  // 插入数据后进行向上调整确保为小堆
            ++usedSize;         // 堆中有效元素加一个
        }
    }
    // 向上调整建立小根堆,时间复杂度是O(n * log2N)
    public void createHeapUp() {
        int[] a = {4, 2, 7, 8, 5, 1, 0, 6};
        // 遍历a数组,将a数组中的元素一个一个的插入堆中
        for (int i = 0; i < a.length; i++) {
            offerHeap(a[i]);
        }
    }
 
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        test2.createHeapUp();
    }
}

运行结果:

 上面我们是一个一个的插入来建立小根堆的,那么我们能不能直接把数组看作是一个堆,然后对数组中的元素进行向上调整——建立小根堆呢?

当然是可以的;

代码示例:

import java.util.Arrays;
 
// 向上调整建立小根堆
public class Main {
    public static void main(String[] args) {
        Main tmp = new Main();
        int[] array =  {4, 2, 7, 8, 5, 1, 0, 6};
        //int[] array = {4, 1, 3, 2, 0};
        tmp.createHeapUp2(array);
        System.out.println(Arrays.toString(array));
    }
    // 向上调整保证是小根堆:时间复杂度是O(log以2为底的N)---log2N
    public  void shiftUp(int[] array, int child) {
        int parent = (child - 1) / 2;
        while (parent >= 0) {
            if (array[child] < array[parent]) {
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
                child = parent;
                parent = (child - 1) / 2;
            }
            else {
                break;
            }
        }
    }
    // 建立小根堆的方法二:
    // 向上调整建立小根堆,时间复杂度是O(n * log2N)
    public  void createHeapUp2(int[] array) {
        for (int i = 1; i < array.length; ++i) {
            shiftUp(array, i); // 对数组的每一个元素进行向上调整
        }
    }
    // 我们向下调整来建堆时,要保证当前要调整的节点的左右子树都已经是堆后,才可以向下调整。
    // 向上调整来建立堆的时候,也是相似的,要保证要调整的节点前面已经是一个合法的堆才行——
    // 为了达到这个目的,我们在向上建堆的时候,就需要从数组的第二个元素开始(也可以理解为第二层的第一个节点)
}
 

运行结果:

注意:

 这个建堆的结果可能和向下调整来建堆一样,但思想其实是不同的;

二、向下调整来建堆

根据该数组建立小根堆 

 

 思路:

因为向下调整的前提是根结点的左右子树均为小堆或大堆才可,而这里,数组为乱序的,无法直接从根结点开始向下调整;

所以我们从倒数第一个非叶结点开始向下调整,从下往上调:

思路分析:
从该解决方案中,我们首先要找到这个倒数第一个非叶结点的数在哪?其实最后一个结点的父亲即为倒数第一个非叶结点。当我们找到这个非叶结点时,把它和它的孩子看成一个整体,进行向下调整。调整后,再将次父节点向前挪动,再次向下调整,依次循环下去。

再回顾下父亲和孩子间的关系:

  • leftchild = parent*2 + 1;
  • rightchild = parent*2 + 2;
  • parent = (child - 1) / 2;

分析过程的图解 :


代码实现:  

import java.util.Arrays;
 
public class Test3 {
 
    public void createHeap(int[] arrays) {
        // 从倒数的第一个非叶子节点开始调节(调节他的左右子树),一直调节到根结点(根节点在数组中的下标为0)
        // 注意这里是usdSize - 1 - 1,因为父亲结点的下标 = (孩子结点的下标 - 1) / 2,
        // 我们是从倒数的第一个非叶子节点的子树开始调节的,而该子树的孩子结点坐标为arrays.length - 1
        for (int i = (arrays.length - 1 - 1) / 2; i >= 0 ; --i) {
            shiftDown(arrays, i); // 从下面的子树一直调到上面的子树
        }
    }
 
    /**
     * 向下调整——使得当前子树为小根堆
     * @param root 是每棵子树的根结点的下标
     * 向下调整的时间复杂度O(log2n)(最坏情况下就是树的高度)
     */
    public void shiftDown(int[] arrays, int root) {
        int parent = root; // 父亲结点的坐标
        int child = 2 * parent + 1; // 获取左孩子结点的坐标
        // 为什么不能child下标要小于usedSize,因为当前数组的最大下标就是usedSize - 1,如果大于或等于usedSize就越界了
        while (child < arrays.length) { // 每个子树在调整的时候,是按从上到下,当child的下标小于usedSize时候就结束
            // 这一步目的是找出孩子结点最小的那个值,然后在让该值和父亲结点比较(不过先要确定孩子结点存在)
            if (child + 1 < arrays.length - 1 && arrays[child] > arrays[child + 1]) {
                child = child + 1;
            }
            if (arrays[child] < arrays[parent]) {
                int tmp = arrays[child];
                arrays[child] = arrays[parent];
                arrays[parent] = tmp;
                parent = child; // 从上向下调整子树,更新父亲结点的下标
                child = 2 * parent + 1; // 更新左孩子孩子结点的下标
            }
            // 因为我们是从上向下调整子树,当我们在调整上面的子树时,下面的子树一定是调整好了的,如果上面都已经满足小根堆,下面也一定满足
            else {
                break; // 此时已经是小根堆了,不需要再次调整,直接退出循环接着调整下一个子树
            }
        }
    }
 
    public static void main(String[] args) {
        Test3 test3 = new Test3();
        int[] a = {4, 2, 7, 8, 5, 1, 0, 6};
        test3.createHeap(a);
        // 注意我们建立的小根堆没有储存在a数组中,而是储存在Test3类中的elem数组中
        System.out.println(Arrays.toString(a));
    }
}

运行结果:

同上面向上调整算法一致;


总结:

1.向下调整来建堆时,要保证当前要调整的节点的左右子树都已经是堆后,才可以向下调整。所以我们要从倒数第一个非叶子结点开始调整
2.向上调整来建立堆的时候,也是相似的,要保证要调整的节点前面已经是一个合法的堆才行——为了达到这个目的,我们在向上建堆的时候,就需要从数组的第二个元素开始(也可以理解为第二层的第一个节点)


三、向上建堆和向下建堆的优劣比较

我们看一下他们各自的时间复杂度 :

举例说明:

有一棵满完全二叉树:

对于向上建堆计算:

 思路分析:

时间复杂度计算的是其调整的次数,根据上文的知识我们已经知晓其是从数组的第二个元素开始的,也就是可以理解为第二层的第一个节点。计算的思想非常简单:计算每层有多少个节点乘以该层的高度次,然后累计相加即可;

如下:

 通过计算得知:向上建堆的时间复杂度O(N*logN);


对于向下建堆计算:

思路分析:

向下调整我们前面已经知道它是从倒数第1个非叶节点开始调整的;

每层的调整次数为:该层的节点个数*该层高度减1;

一直从倒数第2层开始调直至第1层,并将其依次累加,累加的计算过程和向上调整差不多,都是等比*等差的求和;(但不同的是:每层的调整次数不同)

如下:

  通过计算得知:向下建堆的时间复杂度O(n);

 综上,向下建堆其实性能更好一定;


总结

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

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

相关文章

算法训练 | Day41动态规划

343. 整数拆分 思路&#xff1a; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义&#xff1a;dp[i]&#xff1a;分拆数字i&#xff0c;可以得到的最大乘积为dp[i]。 确定递推公式&#xff1a;dp[i] max(dp[i], max((i - j) * j, dp[i - j] * j)) 可以想 dp[i]最…

TimesNet

Key Points 1D变2D 这是本文的核心。大部分现有方法都是作用于时间序列的时间维度&#xff0c;捕获时序依赖性。实际上&#xff0c;现实时间序列一般都有多种模式&#xff0c;比如不同的周期&#xff0c;各种趋势&#xff0c;这些模式混杂在一起。如果直接对原始序列的时间维…

SpringTask任务调度工具的使用

1. Spring Task 1.1 介绍 Spring Task 是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑。 **定位&#xff1a;**定时任务框架 **作用&#xff1a;**定时自动执行某段Java代码 应用场景&#xff1a; 1). 信用卡每月还款提醒 **强调&…

网络机顶盒哪个好?发烧友实测2023网络机顶盒排名

不懂网络机顶盒哪个好的新手们在选购时大部分会参考排行榜&#xff0c;某知名权威数码网站公布了2023年最新网络机顶盒排名&#xff0c;结果是否公正呢&#xff1f;我自费购入了排名前五的网络机顶盒&#xff0c;进行了十多天的深入测评&#xff0c;今天就来详细聊聊我的真实体…

最近部门新的00后真是卷王,工作没1年,入职18K

都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。 这不&#xff0c;前段时间我们公司来了个00后&#xff0c;工作都没1年&#xff0c;到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 …

2.2 逻辑代数中的三种基本运算

学习建议&#xff1a; 理解运算符的含义&#xff1a;首先&#xff0c;我们需要理解每个逻辑运算符的含义。例如&#xff0c;“与”运算符表示两个输入信号同时为真时输出为真&#xff0c;而“或”运算符表示两个输入信号中至少一个为真时输出为真。 练习运算符的应用&#xff…

使用openssl生成https证书

首先去官网下载openssl工具&#xff1a;Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions (slproweb.com) 然后安装完成需要执行&#xff08;1&#xff09;openssl genrsa -des3 -out root.key 1024 &#xff08;2&#xff09;openssl req -new -key ro…

Java面试题总结 | Java面试题总结7- 计算机网络模块(持续更新)

计算机网络 文章目录 计算机网络get和post的区别TCP和UDPTCP和UDP的应用层协议TCP是如何保证可靠传输的视频聊天用的是TCP还是UDP&#xff0c;为什么可靠的UDP协议UDT(UDP-based Data Transfer Protocol)KCPQUIC Cookie和Session的区别Cookie的优缺点cookie的优点&#xff1a;c…

连ChatGPT都不懂的五一调休,到底怎么来的?

今天是周几&#xff1f; 你上了几天班了&#xff1f; 还要上几天班放假&#xff1f; 五一啥安排&#xff1f; 出行的票抢到了吗&#xff1f; 调休到底是谁发明的&#xff1f;&#xff01; 五一劳动节是要劳动吗&#xff1f; 为什么昨天是周一&#xff0c;今天还是周一&a…

跨域融合风口下,又一外资巨头Tier 1+本土供应商“组团”来袭

头部企业正在加速“融合”。 汽车智能化升级对产品创新与多元化需求下&#xff0c;来自技术升级、降本、开发周期缩短等等一系列因素影响&#xff0c;中外供应商们都在积极思变和寻求破局。 随着全球智能汽车产业步入域集中和域融合的新阶段&#xff0c;过去的以硬件&#xf…

找不到vcruntime140_1.dll无法执行的问题解决方案

随着技术的不断进步&#xff0c;人们越来越依赖电脑来处理日常工作。时常在安装或运行一些软件的时候&#xff0c;我们可能会碰到一些提示信息&#xff0c;其中的“找不到vcruntime140_1.dll无法执行”就是很常见的一种。今天我们就来探讨一下这个问题的原因和解决方案。 一.vc…

第八章 C#脚本(上)

脚本是使用 Unity 开发的所有应用程序中必不可少的组成部分。大多数应用程序都需要脚本来响应玩家的输入并安排游戏过程中应发生的事件。游戏对象的行为由附加的组件控制。虽然Unity内置了许多组件&#xff0c;但是我们仍然可以使用脚本来创建自定义组件。Unity支持C#编程脚本语…

用友T3提示,年度[UFDATA_002_2017]并不存在,导致无法备份迁移。

用友T3提示&#xff0c;年度[UFDATA_002_2017]并不存在&#xff0c;导致无法备份迁移。 现在都win11&#xff0c;迁移T3到新电脑&#xff0c;遇到了问题&#xff0c;某个年度账套不存在怎么办&#xff1f; 打开数据管理工具。无法导出。 002代表账套编号&#xff0c;2017代表…

【Zookeeper】 面试题总结

Zookeeper 1、工作中 Zookeeper 有什么用途吗2、zookeeper 数据模型是什么样的3、那你知道 znode 有几种类型呢4、你知道 znode 节点里面存储什么吗5、每个节点数据最大不能超过多少呢6、你知道 znode 节点上监听机制嘛7、那你讲下 Zookeeper 特性吧8、你刚提到顺序一致性&…

计算机网络-基础编程实验(JAVA\Python3)

计算机网络-网络基础编程实验(JAVA\Python3) 一.实验目的 通过本实验&#xff0c;学习采用Socket&#xff08;套接字&#xff09;设计简单的网络数据收发程序&#xff0c;理解应用数据包是如何通过传输层进行传送的。 二.实验内容 学习套接字编程&#xff0c;完成以下的网络数…

海尔牵头IEEE P2786国际标准通过Sponsor投票并连任工作组主席

01 海尔牵头IEEE P2786国际标准 通过Sponsor投票 并连任工作组主席 海尔牵头制定的全球首个服装物联网国际标准IEEE P2786《Standard for General Requirements and Interoperability for Internet of Clothing》通过Sponsor投票&#xff0c;标志着该国际标准草案得到了行业…

ThingsBoard教程(三六):规则节点解析 检查关系节点 check relation,实体类型节点 entity type

前言 本篇文章和大家一起来学习两个节点,检查关系节点 check relation和实体类型节点 entity type。 check relation 检查消息的发起者与其他实体之间是否存在关系。如果选择了“check relation to specific entity(检查与特定实体的关系)”,则必须指定相关实体。否则,…

程序员的4个真相(看了有点想乱搞)

最近在知乎看到一个很有意思的问题&#xff0c;热度还挺高的。 “程序员的通病是什么&#xff1f;” 很多人第一印象就是&#xff1a; - 加班 - 脱发 - 单身直男 - 亚历山大 - 需求魔幻 其实这些标签在其他行业领域也普遍存在&#xff0c;也并不是程序员真正的悲哀。 真正的…

3 程序流程控制-函数、包、常用函数【Go语言教程】

3 程序流程控制-函数、包【Go语言教程】 一、 程序流程控制 顺序控制分支控制switch分支for循环 Go 语言没有 while 和 do…while 语法&#xff0c;这一点需要同学们注意一下&#xff0c;如果我们需要使用类似其它语言(比如 java / c 的 while 和 do…while )&#xff0c;可以…

CVE-2023-28432 MiniO信息泄露漏洞复现

CVE-2023-28432 MiniO信息泄露漏洞 MiniO 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等 在集群部署的 Minio…