数据结构与算法-15高级数据结构_树论(堆树)

news2024/11/23 8:58:42

堆树

1 简介

1.1 什么是堆树

定义:堆树是一种特殊的完全二叉树,其中每个节点的值都遵循一定的堆属性。具体来说,堆分为最大堆和最小堆。

  • 最大堆:在最大堆中,每个父节点的值都大于或等于其任何子节点的值。这意味着根节点是树中的最大值。
  • 最小堆:在最小堆中,每个父节点的值都小于或等于其任何子节点的值。这意味着根节点是树中的最小值。

完全二叉树:除了最后一层外,每一层都被完全填满,且所有节点都尽可能地向左对齐。这种结构使得堆可以用数组来高效地表示和实现。

1.2 如何存储

数组表示:堆通常使用数组来表示,其中根节点位于数组的第一个位置(索引为0或1,取决于具体实现)。对于数组中的任何节点i(假设根节点位于索引1),其左子节点的索引为2i,右子节点的索引为2i+1,父节点的索引则为i/2(整数除法)。

1.3 下标计算
  1. 父节点的下标

对于任意节点,其下标为i(注意,这里的下标可以从0开始,也可以从1开始,具体取决于实现方式),其父节点的下标可以通过以下公式计算:

  • 如果数组下标从0开始:父节点下标 = (i - 1) / 2(整数除法)
  • 如果数组下标从1开始:父节点下标 = i / 2(整数除法)

例如,如果节点下标为5(数组下标从0开始),则其父节点下标为(5 - 1) / 2 = 2

  1. 左子节点的下标

对于任意节点,其下标为i,左子节点的下标可以通过以下公式计算:

  • 如果数组下标从0开始:左子节点下标 = 2 * i + 1
  • 如果数组下标从1开始:左子节点下标 = 2 * i

例如,如果节点下标为2(数组下标从0开始),则左子节点下标为2 * 2 + 1 = 5

  1. 右子节点的下标

类似地,对于任意节点,其下标为i,右子节点的下标可以通过以下公式计算:

  • 如果数组下标从0开始:右子节点下标 = 2 * i + 2
  • 如果数组下标从1开始:右子节点下标 = 2 * i + 1

例如,如果节点下标为2(数组下标从0开始),则右子节点下标为2 * 2 + 2 = 6

2 大顶堆

2.1 堆化图解
2.1.1 基础步骤

在这里插入图片描述

  1. 确定堆化的起始点
    • 在从下往上堆化的过程中,堆化的起始点通常是最后一个非叶子节点。对于一个包含n个元素的数组表示的大顶堆,其最后一个非叶子节点的索引是(n/2) - 1(这里的除法是整数除法,即向下取整)。
  2. 进行堆化操作
    • 从最后一个非叶子节点开始,向上依次对每个节点进行堆化。
    • 对于每个节点,比较它与它的子节点(如果有的话)的值。在大顶堆中,父节点的值应该大于或等于其子节点的值。
    • 如果父节点的值小于任何一个子节点的值,那么需要将父节点与较大的子节点交换。
    • 交换后,可能会破坏下一层的堆性质,因此需要对交换后的子节点继续进行堆化操作,直到堆性质被完全恢复
  3. 重复堆化
    • 对每个非叶子节点重复上述堆化过程,直到到达根节点。
    • 堆化过程是自下而上的,因为每次堆化都是从一个非叶子节点开始,向上调整堆的性质。
2.1.2 示例图解说明

假设有一个数组arr [8 4 20 7 3 1 25 14 17],我们想要将其调整为大顶堆。

  1. 确定堆化的起始点
    • 数组有9个元素,最后一个非叶子节点的索引是(9/2) - 1 = 3
  2. 需要进行堆化的下标集合:[0, 1, 2, 3]
    • 说明我们需要按顺序对 7,20,4,8这四个元素进行堆化
  3. 进行堆化操作
    • 从索引3开始,即元素【arr[3] = 7】,向上进行堆化。
    • 比较【arr[3] = 7】与其子节点【arr[7] = 14】和【arr[8] =17】,发现【arr[3] = 7】小于【arr[7] = 14】和【arr[8] =17】,且【arr[7] = 14】小于【arr[8] =17】,将【arr[8] =17】与【arr[3] = 7】交换。
    • 交换后,新的堆变为[8 4 20 17 3 1 25 14 7],此时交换后的7已经是叶子节点,不需要进一步堆化。
    • 继续对索引2的节点(即元素1)进行堆化
    • 重复上述过程,直到根节点被堆化。

在这里插入图片描述

2.2 实现
package cn.zxc.demo.leetcode_demo.advanced_data_structure.tree_heap;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 大顶堆
 * 堆树使用的是完全二叉树实现,所以可以使用数组存储数据
 * 完全二叉树的特性:
 * 左子树下标 = 父节点下标 * 2 + 1
 * 右子树下标 = 父节点下标 * 2 + 2
 * 父节点下标 = (子节点下标 - 1) / 2
 */
public class LargeTopHead {
    // 数组存储数据
    private int[] arr;
    // 堆的大小
    private int size;

    public LargeTopHead(int capacity) {
        arr = new int[capacity + 1];
        size = -1; // 索引从0开始
    }

    public void add(int value){
        if (size + 1 > arr.length){
            throw new RuntimeException("堆已满");
        }
        arr[++size] = value;
        // 进行堆化
        for (int i = (size - 1) / 2; i >= 0; i--) {
            maxHeap(arr, i, size);
        }
    }

    public void maxHeap(int[] arr, int start, int end){
        int parent = start;
        int son = 2 * parent + 1;
        while (son <= end){
            int temp = son;
            if (son + 1 <= end && arr[son] < arr[son + 1]){
                temp = son + 1;
            }
            if (arr[parent] < arr[temp]){
                int temp1 = arr[parent];
                arr[parent] = arr[temp];
                arr[temp] = temp1;
                parent = temp;
                son = parent * 2 + 1;
                continue;
            }
            // 因为我们堆化是从下到上,所以如果父节点大于子节点,因为子节点已经完成堆化,那么就不用交换了
            return;
        }
    }

    public void printCompleteBinaryTree() {
        if (this.arr == null || size == -1) return;

        Queue<Integer> queue = new LinkedList<>();
        // 初始化,将根节点加入队列
        queue.offer(0);
        int height = (int) Math.ceil(Math.log(size + 1) / Math.log(2)) * 2;
        while (!queue.isEmpty()) {
            int levelSize = queue.size(); // 当前层的节点数
            StringBuffer str = new StringBuffer();
            for (int i = 0; i < height; i++) {
                str.append(" ");
            }
            for (int i = 0; i < levelSize; i++) {
                int currentNode = queue.poll(); // 从队列中取出一个节点
                System.out.print(str.toString() + arr[currentNode] + " "); // 打印节点值

                // 如果存在左子节点,则将其加入队列
                if (2 * currentNode + 1 < size) {
                    queue.offer(2 * currentNode + 1);
                }
                // 如果存在右子节点,则将其加入队列
                if (2 * currentNode + 2 < size) {
                    queue.offer(2 * currentNode + 2);
                }
            }
            height/=2;
            // 当前层打印完毕,换行以便打印下一层
            System.out.println();
        }
    }
}

3 堆排序

3.1 图解

在这里插入图片描述

3.1.1 基础步骤
  1. 建堆(Heapify):对原始数组进行堆化,创建大顶堆或小顶堆【在建堆完成后,堆顶(即数组的第一个元素)就是当前的最大值。】
  2. 交换堆顶与末尾元素:将堆顶元素(即当前最大值)与数组的最后一个元素交换。这样,最大值就被放到了数组的最后,而堆的末尾则变成了一个待排序的“无效”元素。
  3. 调整剩余元素为堆:将剩余的n-1个元素重新调整为大顶堆。这个过程是通过堆化操作来实现的,但此时堆化的范围已经缩小了(因为末尾元素已经是一个“无效”元素,不再参与堆的调整)。
  4. 重复交换与调整:重复上述的交换和调整过程,每次都将新的堆顶元素与当前堆的末尾元素交换,并重新调整剩余的元素为大顶堆。随着过程的进行,堆的大小逐渐减小,而数组的有序部分则逐渐增大。
  5. 排序完成:当堆的大小减为1时,排序过程结束。此时,整个数组已经变成了一个有序数组。
3.1.2 示例图解说明

对2.1.2示例中已完成的大顶堆进行堆排序

堆化后的数组:[25, 17, 20, 14, 3, 1 8, 4, 7]

在这里插入图片描述

  1. 将arr[0] = 25与arr[8] = 7进行交换
  2. 对arr[0]节点进行堆化(注意已交换过的节点(即最后一个节点)不参与对话)
  3. 重复1~2步骤,知道未交换过的节点数为1
  4. 排序完成

在这里插入图片描述

3.2 实现
package cn.zxc.demo.leetcode_demo.advanced_data_structure.tree_heap;

import java.util.Arrays;

/**
 * 堆排序
 * 思想:
 * 1.将数组构建成大顶堆
 * 2.将堆顶元素和最后一个元素交换
 * 3.将已经交换的元素之外的元素重新构建成大顶堆
 * 4.重复2-3步骤直到可交换元素数组为1
 * 时间复杂度:O(nlogn)
 * 核心思想:每一次堆化完成之后,得到堆顶是当前最大的元素,我们将当前最大的元素拿出放在堆尾部,然后重新堆化,重复这个过程,直到数组为空
 */
public class HeadSortDemo {

    public static void main(String[] args) {
        int[] arr = {1, 3, 2, 6, 5, 7, 8, 9, 10, 0};
        HeadSortDemo headSortDemo = new HeadSortDemo();
        for (int i = (arr.length - 1) / 2; i >= 0; i--) {
            headSortDemo.maxHeap(arr, i, arr.length - 1);
        }
//        headSortDemo.headSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public void maxHeap(int[] arr, int start, int end) {
        int parent = start;
        int son = parent * 2 + 1; // 完全二叉树的特性:左子节点下标 = 父节点下标 * 2 + 1
        while (son <= end){
            int temp = son;
            if (son + 1 <= end && arr[son] < arr[son + 1]){
                temp = son + 1;
            }
            if (arr[parent] >= arr[temp]){
                return;
            }else{
                int temp1 = arr[parent];
                arr[parent] = arr[temp];
                arr[temp] = temp1;
                parent = temp;
                son = parent * 2 + 1;
            }
        }
    }

    public void headSort(int[] arr){
        // 从最后一个非叶子节点开始堆化,得到一个大顶堆,但是没有完全排序
        // 说明:
        // 1、最后一个叶子节点的下标 = arr.length
        // 2、根据完全二叉树的特性:左子节点下标 = 父节点下标 * 2 + 1 -> 父节点下标 = (子节点下标 - 1) / 2
        // 3、结论:最后一个非叶子节点的下标 = (数组长度 - 1) / 2
        for (int i = (arr.length - 1) / 2; i >= 0; i--) {
            maxHeap(arr, i, arr.length - 1);
        }
        for (int i = arr.length - 1; i >= 0; i--) {
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;
            maxHeap(arr, 0, i - 1);
        }
    }
}

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

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

相关文章

SpringMVC02

1.拦截器 1.1基本概念 SpringMVC 中的Interceptor拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理。比如通过它来进行权限验证,或者是来判断用户是否登陆等操作。对于SpringMVC拦截器的定义方式 有两种: 实现接口:org.springframework.web.ser…

CAD-文字、图块、多行文字,沿多段线对齐到多段线的顶点,沿直线进行均分,都可以操作

图块和文字对齐直线-均布直线-对齐多段线顶点-旋转平行 (defun c:duiqi () ;将图块与直线对齐&#xff0c;并均分。;先创建的图块排最右;先等分的坐标排最右;刚好对应了(defun MoveToPosition (Blockname p_list / ent refPoint dx dy) ;移动对象到指定坐标(prompt "\nSel…

【Git-驯化】一文学会git中对代码进行存储操作:git stash技巧

【Git-驯化】一文学会git中对代码进行存储操作&#xff1a;git stash技巧 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 免费获取相关内…

@change事件传参

change事件传参 change"(value)>handleChange(value, item,index)" 这样可以接收index参数区分是哪一个组件事件&#xff0c;又可以接收子组件传的value值 <div class"boxItem" v-for"(item, index) in checkPeopleList" :key"inde…

VUE实现TAB切换不同页面

VUE实现TAB切换不同页面 实现效果 资源准备 ReceiveOrderList, TodoListMulti, SignList 这三个页面就是需要切换的页面 首页代码 <template><div><el-tabs v-model"activeTab" type"card" tab-click"handleTabClick"><…

用于相位解包的卷积和空间四向 LSTM 联合网络

原文&#xff1a;A Joint Convolutional and Spatial Quad-Directional LSTM Network for Phase Unwrapping 作者&#xff1a;Malsha V. Perera 和 Ashwin De Silva 摘要&#xff1a; 相位展开是一个经典的病态问题&#xff0c;其目标是从包裹相位中恢复真实的相位。本文&…

鸿蒙(API 12 Beta2版)NDK开发【使用Node-API扩展能力接口】

简介 [扩展能力接口]进一步扩展了Node-API的功能&#xff0c;提供了一些额外的接口&#xff0c;用于在Node-API模块中与ArkTS进行更灵活的交互和定制&#xff0c;这些接口可以用于创建自定义ArkTS对象等场景。 Node-API接口开发流程参考[使用Node-API实现跨语言交互开发流程]…

非负数、0和正整数 限制最大值且保留两位小数在elementpuls表单中正则验证

一、结构 <el-form-item label"单价&#xff1a;" prop"price"><el-inputv-model.trim"formData.price"placeholder"请输入"blur"formMethod.fixTwo"><template #append>(元)</template></el-i…

基础算法:离散化(C++实现)

文章目录 1. 离散化的定义2. 离散化例题2.1 离散化二分2.2 离散化哈希表 1. 离散化的定义 离散化是一种在程序设计和算法优化中常用的技术&#xff0c;其核心思想是将无限空间中有限的个体映射到有限的空间中去&#xff0c;以此提高算法的时空效率。具体来说&#xff0c;离散化…

Docker 安装 GitLab教程

本章教程,主要介绍如何在Docker 中安装GitLab。 GitLab 是一个开源的 DevOps 平台,提供了一整套工具,用于软件开发生命周期的各个阶段,从代码管理到 CI/CD(持续集成和持续交付/部署),再到监控和安全分析。 一、拉取镜像 docker pull gitlab/gitlab-ce:latest二、创建 G…

【React】探讨className的正确使用方式

文章目录 一、className的正确用法二、常见错误解析三、实例解析四、错误分析与解决五、注意事项六、总结 在React开发中&#xff0c;正确使用className属性对组件进行样式设置至关重要。然而&#xff0c;由于JavaScript和JSX的特殊性&#xff0c;开发者常常会犯一些小错误&…

ShardingSphere实战(2)- 水平分表

上篇博客&#xff0c;我们讲了 ShardingSphere实战&#xff08;1&#xff09;- 分库分表基础知识&#xff0c;这篇博客&#xff0c;正式开始实操。 项目环境&#xff1a; JDK11 MySQL 8.0.30 Springboot 2.7.4 Mybatis ShardingSphere HikariCP 连接池 一、Maven 依赖 <…

filebeat发送日志

filebeat: 1.可以在本机收集日志 2.也可以远程收集日志 3.轻量级的日志收集系统&#xff0c;可以在非Java环境运行 logstash是在jvm环境中运行&#xff0c;资源消耗很高&#xff0c;启动一个logstash需要消耗500M左右的内存 filebeat只消耗10M左右的内存 test3是装有logstash的…

C语言的内存布局

根据 C 语言的内存布局规律&#xff0c;通常局部变量和全局变量哪一个的地址更小&#xff1f; 答&#xff1a;如图所示。 下面代码中&#xff0c;为何两个不同的变量可以存放在同一个地址上&#xff1f; #include <stdio.h> void func1(void); void func2(void); voi…

安装 qcloud-python-sts 失败 提示 gbk codecs decode byte 应该如何解决

安装 qcloud-python-sts 失败 提示 gbk codecs decode byte 应该如何解决 解决方案&#xff1a; 将windows 修改为utf-8编码格式 解决步骤如下&#xff1a; 1. 进入控制台 2. 点击区域 4. 点击管理 4.勾选UTF-8 5.重启系统即可

Java零基础之多线程篇:线程间如何通信?

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

计算机技术基础 (bat 批处理)Note6

计算机技术基础 &#xff08;bat 批处理&#xff09;Note6 本节主要讲解FOR命令语句&#xff08;循环&#xff09;在 bat 批处理中的使用 (part 2) 变量延迟 命令语句 在没有开启变量延迟的情况下&#xff0c;批处理命令行中的变量改变&#xff0c;必须到下一条命令才能体现…

C++ STL在算法题中的常用语法

Vector 1.将vector<int>中的元素全部置换为0 fill(vec.begin(), vec.end(), 0); 2.vector容器是可以直接用比较是否值等的&#xff01; Unordered_set 1. unordered_set的删除&#xff08;count的值也会减少&#xff09; 2.unordered_map中的int默认值是0&#xff0c;…

Prometheus-v2.45.0+Grafana+邮件告警

目录 普罗米修斯监控架构介绍 Prometheus 监控架构 1. 数据抓取&#xff08;Scraping&#xff09; 2. 时序数据库&#xff08;TSDB&#xff09; 3. 数据模型 4. PromQL 查询语言 5. 告警&#xff08;Alerting&#xff09; 6. Alertmanager 7. 可视化&#xff08;Visu…

从0开始搭建vue + flask 旅游景点数据分析系统( 六):搭建后端flask框架

这一期开始开发header部分&#xff0c;预期实现两个目标&#xff1a; 创建 Flask 项目导入旅游数据后端实现旅游数据的查询 1 python 环境 & 开发环境 python 安装和pycharm安装需要去网上找包&#xff0c;建议python使用3.8 或者3.9版本 2 新建项目 我们新建一个文件…