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

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

博客首页:未央.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);

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


总结

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android jetpack Compose之约束布局

概述 我们都知道ConstraintLayout在构建嵌套层级复杂的视图界面时可以有效降低视图树的高度&#xff0c;使视图树扁平化&#xff0c;约束布局在测量布局耗时上比传统的相对布局具有更好的性能&#xff0c;并且约束布局可以根据百分比自适应各种尺寸的终端设备。因为约束布局确…

下载——安装——使用FinalShell

下载——安装——使用FinalShell FinalShell简介&#xff1a;下载&#xff1a;使用&#xff1a; FinalShell简介&#xff1a; FinalShell是一款免费的国产的集SSH工具、服务器管理、远程桌面加速的软件&#xff0c;同时支持Windows&#xff0c;macOS&#xff0c;Linux&#xf…

No.050<软考>《(高项)备考大全》【冲刺4】《软考之 119个工具 (2)》

《软考之 119个工具 &#xff08;2&#xff09;》 21.检查:22.偏差分析:23.滚动式规划:24.紧前关系绘图法(PDM):25.确定依赖关系:26.时间提前量与滞后量:28.发布的估算数据:29.自下而上估算:30.项目管理软件:31.储备分析:32.类比估算:33.参数估算:34.三点估算:35.进度网络分析:…

JS实现拼音(字母)匹配(搜索)汉字(姓名)

这就是个模糊查询&#xff0c;我们平常做的都是直接输入汉字去把对应的值过滤出来&#xff0c;但我还真是第一次通过拼音去查询&#xff08;当然不只是拼音&#xff0c;汉字也是可以的&#xff09;&#xff0c;以前还真没注意这个。唉&#xff0c;这可咋搞&#xff0c;我怎么知…

使用Statsmodel进行假设检验和线性回归

如果你使用 Python 处理数据&#xff0c;你可能听说过 statsmodel 库。Statsmodels 是一个 Python 模块&#xff0c;它提供各种统计模型和函数来探索、分析和可视化数据。该库广泛用于学术研究、金融和数据科学。在本文中&#xff0c;我们将介绍 statsmodel 库的基础知识、如何…

(七)ArcCatalog应用基础——图层操作与数据输出

&#xff08;七&#xff09;ArcCatalog应用基础——图层操作与数据输出 目录 &#xff08;七&#xff09;ArcCatalog应用基础——图层操作与数据输出 1.地图与图层操作1.1创建图层1.2设置文件特征1.3保存独立的图层文件 2.地理数据输出2.1输出为Shapefile2.2输出为Coverage2.3属…

笔记本电脑没有声音了怎么恢复

笔记本电脑 在使用的过程中&#xff0c;突然没有声音的话&#xff0c;对于人们来说会很麻烦。那么笔记本电脑没有声音了怎么恢复呢?下面小编为大家整理了笔记本电脑没有声音的恢复方法&#xff0c;一起来看看吧。 方法/步骤&#xff1a; 方法一&#xff1a;网络适配器检查音频…

UE5实现建筑剖切效果

文章目录 1.实现目标2.实现过程2.1 材质参数集2.2 材质遮罩函数2.3 更新Box3.参考资料1.实现目标 基于BoxMask材质节点,在UE5中实现建筑物的剖切效果,GIF动图如下: 2.实现过程 实现原理与之前“BoxMask实现建筑生长效果”的原理相同,都是基于BoxMask材质节点实现。 具体实…

操作系统之内存管理

连续分配 一、单一连续 直接为要运行的进程分配一个内存&#xff0c;只适合单任务&#xff0c;只能用于单对象、单任务&#xff0c;内存被分配为系统区和用户区&#xff0c;系统区在低地址&#xff0c;用户区是一个用户独享 二、等分分区 由于分配一个内存只能执行单任务&a…

MongoDB【常用命令】

目录 1&#xff1a;基本常用命令 1.1&#xff1a;演示案例 1.2&#xff1a;数据库操作 1.2.1&#xff1a;选择和创建数据库&#xff0c;查看当前正在使用的数据库命令 1.2.2&#xff1a;数据库的删除 1.3&#xff1a;集合操作 1.3.1&#xff1a;集合的显式创建&#xff0…

C++ srand()和rand()用法

参考C rand 与 srand 的用法 计算机的随机数都是由伪随机数&#xff0c;即是由小M多项式序列生成的&#xff0c;其中产生每个小序列都有一个初始值&#xff0c;即随机种子。&#xff08;注意&#xff1a; 小M多项式序列的周期是65535&#xff0c;即每次利用一个随机种子生成的随…

【机器学习】HOG+SVM实现行人检测

文章目录 一、准备工作1. 下载数据集2. 解压数据集 二、HOG特征简介1. 梯度&#xff08;Gradient&#xff09;2. 格子&#xff08;Cell&#xff09;3. 块归一化&#xff08;Block Normalization&#xff09;4. HOG特征&#xff08;HOG Feature&#xff09;5. 使用skimage.featu…

docker容器原理及简单且详细的使用

docker原理简单介绍 docker是一种虚拟化容器技术。 虚拟化&#xff1a;早期为了节约成本和学习只有在宿主机中基于 kvm&#xff08;基于内核的虚拟机&#xff09;等技术虚拟出来完整的操作系统&#xff0c;而这个完整的操作系统会大量的占用宿主机的硬件资源&#xff0c;当创建…

Oracle LiveLabs实验:DB Security - Data Masking and Subsetting (DMS)

概述 本实验介绍了适用于 Enterprise Manager 的 Oracle 数据屏蔽和子集 (DMS) 包的各种特性和功能。 它使用户有机会学习如何配置这些功能&#xff0c;以便在非生产环境中保护他们的敏感数据。 此实验申请地址在这里&#xff0c;时间为60分钟。 本实验也是DB Security Adva…

无惧黑暗强光,纯视觉导航也能全天候作业

对于一台激光导航扫地机器人而言&#xff0c;全天候作业并非难事&#xff0c;那么纯视觉导航扫地机器人能做到吗&#xff1f; 无论对于人&#xff0c;还是机器人&#xff0c;光线环境的变化对“眼睛”的影响都是致命的。由于视觉传感器对于光线十分敏感&#xff0c;在家庭场景…

linux入门---软硬链接

软链接 使用指令ln -s 被链接的文件 生成的软链接文件 便可以创建软连接文件&#xff0c;ln是link的简写表明当前要创建链接文件&#xff0c;s是soft的简写表明当前创建的链接文件为软链接文件&#xff0c;然后加上被链接的文件&#xff0c;最后写上生成的链接文件的文件名比如…

使用 ArcGIS Pro 进行土地利用分类的机器学习和深度学习

随着技术进步,尤其是地理信息系统 (GIS)工具的进步,可以更有效地对土地利用进行分类。分类的使用可用于识别植被覆盖变化、非法采矿区和植被抑制区域,这些只是土地利用分类的众多示例中的一部分。 分类的一大困难是确定要解决的问题的级别。我分类的目的是什么?分类是否需…

【科普知识】电机的10种工作制说明:S1~S10

如今&#xff0c;在我们的生活中&#xff0c;电机几乎无处不在&#xff0c;从国防、工农、运输、临床器械、通讯到生活中的洗衣机、风扇、吸尘器、电动机器人等&#xff0c;都在应用着各式各样的电动机。 电机作为一种能够将电能转换成机械能的装置&#xff0c;是现代工业生产和…

【搭建私有云盘】无公网IP,在外远程访问本地微力同步

文章目录 1.前言2. 微力同步网站搭建2.1 微力同步下载和安装2.2 微力同步网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 私有云盘作为云存储概念的延伸&#xff0c;虽然谈不上多么新颖&#xff0c;但是其…

快速上手非关系型数据库Redis

一、Redis介绍 1.非关系型数据库&#xff0c;纯内存操作&#xff0c;key-value存储&#xff0c;性能很高&#xff0c;可持久化&#xff08;内存---->保存到硬盘上&#xff09; 2.缓存&#xff0c;计数器&#xff0c;验证码&#xff0c;geo地理位置信息&#xff0c;发布订阅…