首页 > 编程语言 > 数据结构之二叉堆(Java)

数据结构之二叉堆(Java)

时间:2023-11-24 22:32:37浏览次数:43  
标签:10 Java 之二 孩子 二叉 childIndex array 数据结构 节点

一:概述

二叉堆:二叉堆本质上是一种完全二叉树,它分为两个类型。

  • 最大堆
  • 最小堆

二:最大堆与最小堆的具体说明

<1>最大堆

最大堆的任何一个父节点的值,值大于或等同于它左、右孩子节点的值。例子如下图所示:

                                                 数据结构之二叉堆(Java)_父节点

<2>最小堆

最小堆的任何一个父节点的值,都小于或等于它的左、右孩子节点的值。例子如下图所示:

                                                 数据结构之二叉堆(Java)_最小堆_02

二叉堆的根节点叫做堆顶

最大堆和最小堆的特点决定了:最大堆的堆顶是整个堆中的最大元素;最小堆的堆顶是整个堆中最小的元素

三:二叉堆的自我调整

二叉堆有以下的几种操作。

  • 插入节点
  • 删除节点
  • 构建二叉堆

这几种操作都基于堆的自我调整。所谓堆的自我调整,就是把一个不符合堆性质的完全二叉树,调整成一个堆。下面以最小堆为例子,来看看二叉堆是如何进行自我调整的。

<1>插入节点

当二叉堆插入节点时,插入位置是完全二叉树的最后一个位置。例如插入一个新的节点,值为0.

                                                 数据结构之二叉堆(Java)_二叉堆_03

这时,新节点的父节点5比0大,这不符合最小二叉堆的性质。所以让新节点“上浮”,和父节点交换位置。

                                                 数据结构之二叉堆(Java)_父节点_04

继续用节点0和父节点3做比较,因为0小于3,则让新节点继续“上浮”。

                                                 数据结构之二叉堆(Java)_父节点_05

继续比较,最终节点0“上浮”到了堆顶的位置。

<2>删除节点

二叉堆删除节点和插入节点的过程正好相反,所以删除的是处于堆顶的节点。例如删除最小堆的堆顶点1.

                                                 数据结构之二叉堆(Java)_父节点_06

为了继续维持完全二叉树的结构,我们把堆的最后一个节点10临时补到原本堆顶的位置。

                                                 数据结构之二叉堆(Java)_父节点_07

接下来,让暂处堆顶位置的节点10和它的左、右孩子进行比较,如果左、右孩子节点种最小的一个(显然是节点2)比节点10小,那么让节点10“下沉”。

                                                 数据结构之二叉堆(Java)_二叉堆_08

继续让节点10和它的左、右孩子做比较,左、右孩子种最小的是节点7,由于10大于7,让节点10继续“下沉”。

                                                 数据结构之二叉堆(Java)_最小堆_09

二叉堆得到了重新的调整。

<3>构建二叉堆

构建二叉堆,也就是把一个无序的完全二叉树调整为二叉堆,本质就是让所有非叶子节点依次“下沉”

下面是一个无序完全二叉树的例子,如下图所示。

                                                 数据结构之二叉堆(Java)_最小堆_10

首先,从最后的一个非叶子点开始,也就是从节点10开始。如果节点10大于它的左、右孩子节点中最小的一个,则节点10“下沉”。

                                                 数据结构之二叉堆(Java)_最小堆_11

接下来是节点3,如果节点3大于它的左、右孩子节点中最小的一个,则节点3“下沉”。

                                                 数据结构之二叉堆(Java)_最小堆_12

然后是节点1,如果节点1大于它的左、右孩子节点中最小的一个,则节点1“下沉”。在这个例子中,节点1是小于它的左、右孩子节点的,所以不用改变。接下来是节点7,如果节点7大于它的左、右孩子节点中最小的一个,则节点7“下沉”。

                                                 数据结构之二叉堆(Java)_最小堆_13

节点7继续比较,继续“下沉”。

                                                 数据结构之二叉堆(Java)_二叉堆_14

经过上述几轮比较“下沉”后,最终每一父节点都小于它的左、右孩子节点,一个无序的完全二叉树就被构建成了一个最小堆。

堆的插入操作操作是单一节点的“上浮”,堆的删除操作是单一节点的“下沉”,这两个操作的平均交换次数都是堆高度的一半,所以时间复杂度是O(logn),至于堆的构建,需要所有非叶子点依次“下沉”,所以时间复杂度你可能想的是O(logn),但是构建堆的时间复杂度不是O(nlogn),而是O(n)。这里涉及数学推导,有兴趣的可以想一下。

四:二叉堆的代码实现

在写代码之前,还需要明确:二叉堆虽然是一个完全二叉树,但它的存储方式并不是链式存储,而是顺序存储。换句话说,二叉堆的所有节点都存储在数组中。

                                                 数据结构之二叉堆(Java)_父节点_15

在数组中,在没有左、右指针的情况下,如何定位一个父节点的左孩子和右孩子呢?

根据向上图这样,可以依靠数组下标来计算。

假设父节点的下标是parent,那么它的左孩子下标就是 2 x parent + 1;右孩子下标就是2 x parent + 2

假设上面的例子中,节点4包含节点9和节点10两个孩子节点,节点4在数组中的下标是3,节点9在数组中的下标是7,节点10在数组中的下标是8.

那么:

7 = 3 x 2 + 1

8 = 3 x 2 + 2

发现刚好符合规律。有了这个前提,代码能好理解一点。

package com.algorithm2;


import java.util.Arrays;

/**
 * 二叉堆
 */
public class BinaryHeap {

    /**
     * "上浮"调整
     * @param array  待调整的堆
     */


    public static void upAdjust(int[] array) {
        // 定义孩子索引
        int childIndex = array.length - 1;
        // 定义父亲节点索引
        int parentIndex = (childIndex - 1) / 2;
        // temp保存在插入叶子节点的值,用于最后的赋值
        int temp = array[childIndex];
        // 当孩子索引大于0,并且临时保存插入叶子节点的值小于父亲节点的值时
        while (childIndex > 0 && temp < array[parentIndex])
        {
        // 无须真正交换,单向赋值即可
        array[childIndex] = array[parentIndex];
        // 孩子索引等于父亲节点索引
        childIndex = parentIndex;
        // 父亲节点索引等于(父亲节点索引 - 1 )的一半
        parentIndex = (parentIndex - 1) / 2;
        }
        // 将temp赋值给孩子节点对应索引的值
        array[childIndex] = temp;
    }

    /**
     * “下沉”调整
     * @param array 待调整的堆
     * @param parentIndex  要“下沉”的父节点
     * @param length 堆的有效大小
     */

     public static void downAdjust(int[] array, int parentIndex,int length) {
        // temp保存父节点的值,用于最后的赋值
         int temp = array[parentIndex];
        // 令孩子节点索引等于父亲节点索引的2倍 + 1
         int childIndex = parentIndex * 2 + 1;
         while (childIndex < length) {
             // 如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
             if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {
                 childIndex++;
             }
             // 如果父节点小于任何一个孩子节点的值,则直接跳出
             if (temp <= array[childIndex])
                 break;
             // 无须真正交换,单向赋值即可
             array[parentIndex] = array[childIndex];
             parentIndex = childIndex;
             childIndex = 2 * childIndex + 1;
             array[parentIndex] = temp;
         }
     }

    /**
     * 构建堆
     * @param array 待调整的堆
     */

     public static void buildHeap(int[] array) {
         // 从最后一个非叶子点开始,依次做“下沉"调整
         for (int i = (array.length - 2) / 2; i >= 0; i--) {
             downAdjust(array, i, array.length);
         }
     }


  public static void main(String[] args) {
         int[] array = new int[] {1, 3, 2, 6, 5, 7, 8, 9, 10, 0};
         upAdjust(array);
         System.out.println(Arrays.toString(array));

         array = new int[] {7, 1, 3 ,10, 5, 2, 8, 9, 6};
         buildHeap(array);
         System.out.print(Arrays.toString(array));
  }

}

在这里代码有一个需要优化的点,就是在父节点和孩子节点做连续交换时,并不一定要真的交换

只需要先把交换的一方的值存入temp变量,最单向覆盖,循环结束之后,再把temp的值存入

交换后的最终位置即可。

标签:10,Java,之二,孩子,二叉,childIndex,array,数据结构,节点
From: https://blog.51cto.com/u_15912723/8551825

相关文章

  • Java中常用的加密方式
    加解密算法应用场景加解密是什么?为什么要加密?加密类型都有哪些?有万能加密么?1)加密,顾名思义,添加密码,密码的作用是加密保护和安全认证。如果没有加密,即明文显示,那么很容易导致信息泄露;加密之后,未经授权的用户即使获得了信息,但不知秘钥,仍然无法了解信息的具体内容。2)加密算法大体......
  • JavaScript存在更新不存在插入操作
    [Updateifexistsoraddnewelementtoarrayofobjects-elegantwayinjavascript+lodash-StackOverflow](https://stackoverflow.com/questions/25764719/update-if-exists-or-add-new-element-to-array-of-objects-elegant-way-in-javascr)```js functionu......
  • 西北电专电院_数据结构上机报告记录_第三次上机报告
    内容比较简单,和其他院的上机比起来说是这样的实现二叉树的基本操作,二叉树使用链式结构建立,基本操作基本用递归实现 1.问题描述二叉树的基本操作;(1)创建二叉树,需注意此处是按照先序法输入(2)通过先序遍历、中序遍历、后序遍历分别输出二叉树(3)求取二叉树的结点总数、树的深度......
  • java 拼图游戏
    界面代码packagenet.elaina.ui;importjavax.swing.*;importjavax.swing.border.BevelBorder;importjava.awt.event.ActionEvent;importjava.awt.event.ActionListener;importjava.awt.event.KeyEvent;importjava.awt.event.KeyListener;importjava.util.Ra......
  • JavaScript获取随机数
    获取随机数 Math.random()constnum=Math.random();此代码作用是获取一个范围在[0,1)之间的随机数。若要获取[100,1000)之间的随机数,可以通过一下方法:先获取[0,1000)之间的随机数constnum=Math.random()*1000;然后获取[100,1000)之间随机数//num1的范围是[0.1,1)const......
  • Java Learning Day1 关键字、标识符、注释、变量
    其实之前也学习过两个月的JAVA,跟着淘宝上买的王道Java课,每天看了1day,整个过程下来感觉什么都没有掌握,所以现在就打算重新学一次,从最开始的关键字开始,也就开通了博客,希望这次学习可以多多掌握一些吧。  关键字:小写、含有特殊含义的单词 标识符:方法名、类名、参数名、变量名......
  • Java泛型Generics​入门详解
    Java泛型Generics泛型基础知识泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。泛型的格式:<数据类型>注意:泛型只能支持引用数据类型。如果我们没有给集合指定类型,默认认为所有的数据类型都是Object类型,此时可以往集合中添加任意的数据类型。带来一个坏处是由于......
  • Java登陆第十三天——网络编程(三)DatagramSocket
    DatagramSocket使用DatagramSocket(数据套接字)可以进行UDP程序的开发,此类可以建立单向地、不可靠地、快速地通信。在UDP编程中,混淆了服务端和客户端的概念。因为通信是单向的,所以身份可以随时切换。(也有人把TCP称作服务端客户端,UDP称作发送端和接收端)DatagramSocket类常用......
  • java多线程学习之路-不能理解
    1importjava.util.concurrent.CountDownLatch;23/**4*颠覆理解的,为什么不会出问题,执行多次,结果都是正确,并且一致5*/6classMyData{7inta=5;//可预定总座位数8intb=0;//已预定座位数910publicvoidyd(){11if(b<......
  • Java Web实现文件下载的几种方式
    文件下载可以说是网站的基础功能,要实现最下载功能,有一种最基本的方法,那就是将超链接的href属性指向对应的资源文件。如下面连接指向了百度首页的图片:​ ​I'mtheindexofBaidu​​但这种方式的缺陷也是很明显的,目录信息被获取,不利于信息安全。其实信息安全还是其次,主要还是......