二叉树的顺序结构以及堆的实现——【数据结构】
W...Y的主页 😊
代码仓库分享 💕
上篇文章,我们认识了什么是树以及二叉树的基本内容、表示方法……接下来我们继续来深入二叉树,感受其中的魅力。
目录
二叉树的顺序结构
堆的概念及结构
堆的实现
堆的创建
堆的初始化与释放空间
堆的插入
堆的删除
堆实现的代码接口,以及简单函数的直接实现
二叉树的顺序结构
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
顺序存储又叫数组存储,是指一层一层存入数组中去。物理层面上是一个数组,在逻辑上面是一个二叉树!!!
而在顺序存储中有一些规律:
通过父节点可以找到自己左右两个孩子:
左孩子:leftchild = parent*2+1;
右孩子:rightchild = parent*2+2;
通过孩子节点可以找到父节点:
parent = (child-1)/2;
因为我们将数据按照二叉树的规律存入数组中(从上到下、从左往右)所以父节点与子节点就有一些特殊的规律,这个是我们经常会用到的,需要我们熟记于心!!!
总结:如果强行将一个普通的二叉树用数组存储,会浪费许多空间。所以只有满二叉树与完全二叉树才可以进行数组顺序存储。
堆的概念及结构
这个堆与操作系统中的堆不同,操作系统将内存存储划分为栈区、堆区、静态区……而这个堆是一种数据结构。
堆的概念:
如果有一个关键码的集合K = {k0 ,k1 ,k2 ,…,kn-1 },把它的所有元素按完全二叉树的顺序存储方式存储
在一个一维数组中,并满足: ki<=k(2*i+1) 且ki <=k(2*i+2) ( ki>=k(2*i+1) 且 ki>=k(2*i+2)) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。(以上k后面的数字以及表达式全部为角标i)
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
小堆:树中任何一个父亲的值都<=孩子的值
大堆:树中任何一个父亲的值都>=孩子的值
小堆那堆的底层数组是否为升序呢?
不一定!
这个堆转换成数组为:10 15 56 25 30 70明显不是升序,堆只能保证父亲小于(大于)孩子,并不是与堂兄第比较。但是我们可以发现小堆的根是整棵树中最小值。
所以我们可以利用发现解决topk问题与堆排序。
堆排序是非常快的,时间复杂度只有O(N*logN)
堆的实现
堆的创建
想要实现堆,我们先来表示堆,堆的创建其实就与顺序表大同小异,因为在物理层面上就是一个数组。
typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;
创建指针指向将要动态开辟的数组中,size记录有效存储数据个数,capacity记录开辟空间大小。
堆的初始化与释放空间
还是与顺序表相同,我们将堆中指针置空,size与capacity赋值0即可。
void HeapInit(HP* php)
{assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}
释放空间也非常简单,free掉开辟的空间,指针置空,size与capacity赋值0即可。
void HeapDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}
堆的插入
堆的实现原理是数组,所以我们使用尾插非常方便。但是堆的要求是父亲节点必须大于或小于孩子节点,所以我们在插入时有很多情况。就以小堆为例:
这里最坏的情况就是插入一个数,最后与根交换才能成为小堆。这里我们可以创建一个向上调整函数,当插入一个数据时,我们得进行比较调换让其继续保存小堆!!!
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
我们传入这个数组,再将新插入的数据下标给予向上调整函数,利用二叉树在数组中存储的规律找到父节点进行比较,如果子节点小于父节点则break退出循环,反之子节点大于父节点进行交换继续寻找交换后的父节点进行比较,直到满足小堆或child>0结束循环。
Swap交换函数:
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
而创建插入函数就与顺序表极为相似,先判断空间是否足够,然后将新的数据插入数组中,最后调用向上调整判断是否满足小堆条件。
void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->capacity == php->size){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc");exit(-1);}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);
}
堆的删除
堆的删除中,我们删除数组的尾元素就没有意义,所以一般删除堆是删除堆顶元素。那怎么删除才能保证满足小堆条件呢?
我们不能直接将堆顶元素删除,然后将数组中的其他元素往前挪一位,这样有极大的可能不满足堆的条件。如果按照上述方法继续进行,然后从新使用向上调整进行排序这样时间复杂度会很高,那有什么方法可以优化呢?
我们可以将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调
整算法。
将堆顶的元素交换到下面去先进行尾删覆盖,再进行向下调整就可以完成堆顶的删除。
那如何创建向下调整函数呢?
我们先创建将数组指针进行接收,然后传入数组的大小以及已经交换过需要调整元素的下标0
然后通过二叉树在数组中存储的规律找到左孩子节点,让左孩子与右孩子进行比较,谁小谁才有机会与父节点进行比较交换。以此类推即可满足小堆要求。
void AdjustDown(HPDataType* a, int n, int parent) {int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child + 1] < a[child]){child++;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}} }
特别注意:我们在找到左孩子节点之后,要让左孩子与右孩子进行比较时,我们必须要加入限制条件child+1<n,否则会造成数组越界访问的后果!!!
而删除堆函数就非常简单了,只需要将头与尾进行交换后调用向下调整函数即可。
void HeapPop(HP* php)
{assert(php);assert(php->size > 0);Swap(&php->a[0], &php->a[php->size - 1]);--php->size;AdjustDown(php->a, php->size, 0);
}
堆实现的代码接口,以及简单函数的直接实现
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{assert(php);assert(php->size > 0);return php->a[0];
}
// 堆的判空
int HeapEmpty(Heap* hp);
{assert(hp);return hp->size == 0;
}
以上就是二叉树的顺序结构以及堆排序实现的全部内容,下一篇文章我们就要学习关于堆最经典的堆排序以及topk问题,敬请期待。
感谢大家观看,一键三连是对博主最大的鼓励,博主因为你们的阅读而开心,希望博主的分享可以帮助许多人❤️❤️❤️
相关文章:

二叉树的顺序结构以及堆的实现——【数据结构】
W...Y的主页 😊 代码仓库分享 💕 上篇文章,我们认识了什么是树以及二叉树的基本内容、表示方法……接下来我们继续来深入二叉树,感受其中的魅力。 目录 二叉树的顺序结构 堆的概念及结构 堆的实现 堆的创建 堆的初始化与…...

手写一个摸鱼神器:使用python手写一个看小说的脚本,在ide中输出小说内容,同事直呼“还得是你”
文章目录 一、准备python环境二、分析小说网的章节目录三、分析小说网的章节内容四、编写python脚本五、验证一下吧 一、准备python环境 windows从0搭建python3开发环境与开发工具 Python爬虫基础(一):urllib库的使用详解 Python爬虫基础&a…...

【Python 实战】---- 实现批量图片的切割
1. 需求场景 在实际开发中,我们会遇到一种很无聊,但是又必须实现的需求,就是比如协议、大量的宣传页面、大量的静态介绍页面、或者大量静态页面,但是页面高度很高,甚至高度可能会达到50000px,但是为了渲染…...

MAYA粒子基础_场
重力场 牛顿场 径向场 均匀场和重力场的区别 空气场 推动物体 阻力场 推动物体 涡流场 湍流场 体积轴场...

趣解设计模式之《我买了宝马,为啥不让我停这?》
〇、小故事 我们怎么识别一辆汽车是宝马品牌的汽车呢?虽然宝马汽车车辆型号非常的多,而且外型也各不相同,但是只要是宝马品牌的汽车,它的车头一定会有宝马汽车的logo,那么这个就是大家最直观去确认一辆车是不是宝马牌…...
MyBatis Plus 中 LocalDateTime 引发的一些问题和解决办法
简介 在使用 MyBatis Plus 进行数据库操作时,我们经常会遇到处理日期时间类型的需求。然而,在某些情况下,使用 LocalDateTime 类型可能会引发一些问题。本文将详细介绍这些问题,并提供相应的解决办法。 问题描述: 1…...

谁懂啊!自制的科普安全手册居然火了
自制的科普安全手册居然火了 谁懂啊! 嗨嗨嗨!小仙女们,有没有见过这样的可以翻页的电子安全手册呢?自己随手就能轻松制作手册,结果一晚浏览量这么多!这可真是让人又惊又喜啊!快来分享一下我的喜…...

强化学习-论文调研-泛化性能力度量
1.[ICML2019]Quantifying Generalization in Reinforcement Learning 文章提出16000多个单智能体闯关游戏CoinRun,通过智能体在分割开的训练环境和测试环境上表现的性能作为RL泛化性的度量。具体而言作者通过”奔跑硬币泛化曲线“ (CoinRun Gener…...

CSS中图片旋转超出父元素解决办法
下面的两种解决办法都会导致图片缩小,可以给图片进行初始化的宽高设置 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge">…...
QML、C++ 和 JS 三者之间的交互
QML、C++ 和 JS 三者之间的交互是 Qt Quick 应用开发的核心。以下是它们之间交互的常见方式: 从 QML 调用 C++ 函数要从 QML 调用 C++ 函数,您可以使用 Qt 的 QML 注册机制,例如 qmlRegisterType,将 C++ 类注册为 QML 类型。 C++ 代码: #include <QGuiApplication>…...

ProEasy机器人:TCP无协议通讯(socket通讯)时打印log日志
打印日志需要调用lua中的io相关文件函数与os相关时间函数,代码如下 --------TCP无协议视觉通讯------- function open_client_Vision() --连接视觉服务器 打开以太网作为客户端 repeat FreePort.ECM_CloseAll() --关闭所有链接 …...

算法通过村第六关-树白银笔记|层次遍历
文章目录 前言1. 层次遍历介绍2. 基本的层次遍历与变换2.1 二叉树的层次遍历2.2 层次遍历-自底向上2.3 二叉树的锯齿形层次遍历2.4 N叉树的层次遍历 3. 几个处理每层元素的题目3.1 在每棵树行中找出最大值3.2 在每棵树行中找出平均值3.3 二叉树的右视图3.4 最底层最左边 总结 前…...
SpringCloud理解篇
一、微服务概述 1、什么是微服务 目前的微服务并没有一个统一的标准,一般是以业务来划分将传统的一站式应用,拆分成一个个的服务,彻底去耦合,一个微服务就是单功能业务,只做一件事。 与微服务相对的叫巨石 。 2、微服…...
编写LED灯的驱动,实现三盏灯的控制
mychrdev.c #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> #include "head.h"unsigned int major; // 保存主设备号 char kbuf[128]{0}; unsigned int…...
Flink报错处理-1
在 flink job 运行一段时间后,观察日志发现出现了如下的 warn日志: The operator name {} exceeded the {} characters length limit and was truncated 完整的 warn 日志如下: The operator name TriggerWindow(GlobalWindows(), ListStat…...

bim与数字孪生智能建造的关系
随着建筑业数字化改革的推进,我们正迈入数字孪生时代,而真正实现建筑物数字孪生的智能建造,其基础前提是建造对象和建造过程的高度数字化,这样一个过程唯有依托BIM建立数据模型才能实现,真正达到智能建造或智慧运维。 …...
【Linux】进程篇(补):守护进程
文章目录 1. 补充1.1 查看1.2 控制进程组的方式 2. 创建守护进程step1. 忽略信号step2. 让自己不是组长step3. setsid 函数:给调用函数设置新的会话和进程组 IDstep4. chdir 函数:可以改变守护进程的工作路径step5. 处理文件描述符 0、1、2 守护进程类样…...
SpringMVC自定义视图完成步骤 和 视图解析的源码剖析
自定义视图完成步骤: ● 7.2.1自定义视图完成步骤 1. 自定义视图**:** 创建一个 View 的 bean, 该 bean 需要继承自 AbstractView, 并实现 renderMergedOutputModel 方法**.** 2. 并把自定义 View 加入到 IOC 容器中 3. 自定义视图的视图处理器,使用…...
合宙Air724UG LuatOS-Air lvgl字库
目录 LVGL 简介1. lvgl自带字库 特点使用场景2. lvgl加载外部字体 软件接口使用场景3. lvgl 矢量字体 软件接口硬件外接SPI字库芯片详细使用示例使用场景常见问题 LVGL 简介 LVGL字库有3种方式可以使用,刚接触的客户可能不太了解怎样选用,以下对这3种…...

C#,数值计算——指数微分(exponential deviates)的计算方法与源程序
1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// 指数偏差 /// Structure for exponential deviates. /// </summary> public class Expondev : Ran { private double beta { get; set; } /// <s…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...

【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...

云原生安全实战:API网关Envoy的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关 作为微服务架构的统一入口,负责路由转发、安全控制、流量管理等核心功能。 2. Envoy 由Lyft开源的高性能云原生…...

【阅读笔记】MemOS: 大语言模型内存增强生成操作系统
核心速览 研究背景 研究问题:这篇文章要解决的问题是当前大型语言模型(LLMs)在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色,但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成(RA…...

RabbitMQ 各类交换机
为什么要用交换机? 交换机用来路由消息。如果直发队列,这个消息就被处理消失了,那别的队列也需要这个消息怎么办?那就要用到交换机 交换机类型 1,fanout:广播 特点 广播所有消息:将消息…...
Qt学习及使用_第1部分_认识Qt---Qt开发基本流程
前言 学以致用,通过QT框架的学习,一边实践,一边探索编程的方方面面. 参考书:<Qt 6 C开发指南>(以下称"本书") 标识说明:概念用粗体倾斜.重点内容用(加粗黑体)---重点内容(红字)---重点内容(加粗红字), 本书原话内容用深蓝色标识,比较重要的内容用加粗倾…...