数据结构——二叉树的顺序存储(堆)(C++实现)

news/2024/5/13 16:42:37

数据结构——二叉树的顺序存储(堆)(C++实现)

  • 二叉树可以顺序存储的前提
  • 堆的定义
  • 堆的分类
    • 大根堆
    • 小根堆
  • 整体结构把握
  • 两种调整算法
    • 向上调整算法
      • 递归版本
    • 非递归版本
    • 向下调整算法
    • 非递归版本
  • 向上调整算法和向下调整算法的比较

我们接着来看二叉树:

二叉树可以顺序存储的前提

完全二叉树完全符合顺序存储的前提:

完全二叉树:顺序存储二叉树最适合应用于完全二叉树。完全二叉树是一种特殊的二叉树,除最后一层外,每一层都被完全填满,并且所有结点尽可能集中在左边。由于其结构特性,完全二叉树的结点与数组下标之间存在着直接的数学映射关系,**使得每个结点可以按照固定规则(如左孩子为2i,右孩子为2i+1)**在数组中找到其相应的位置。这种映射保证了数组的存储空间得以充分利用,没有浪费。

顺序存储的二叉树我们称为

堆的定义

堆是一种特殊的树形数据结构,通常以数组的形式进行顺序存储。堆具有以下关键性质:

  1. 完全二叉树结构
    堆是一个完全二叉树或近乎完全二叉树。这意味着除了可能的最后一层外,其他各层都是完全填充的,并且最后一层的所有结点都尽可能靠左排列。这种结构非常适合用数组来表示,因为完全二叉树的结点与数组下标之间存在直接的数学映射关系,使得每个结点可以高效地通过下标访问。

  2. 堆序性质
    堆分为两种主要类型:最大堆和最小堆。无论哪种类型,堆都遵循特定的堆序性质:

  • 大根堆:每个结点的值都大于或等于其子结点的值。即对于任意结点 i,其值 A[i] 大于等于其左孩子 A[2*i+1] 和右孩子 A[2*i+2] 的值。
  • 小根堆:每个结点的值都小于或等于其子结点的值。即对于任意结点 i,其值 A[i] 小于等于其左孩子 A[2*i+1] 和右孩子 A[2*i+2] 的值。

由于堆的完全二叉树特性和堆序性质,它非常适合使用数组进行顺序存储。具体来说:

  • 数组下标与结点关系

假设数组 A 存储了一个堆,根结点位于下标 0。那么对于任一结点 i,其左孩子、右孩子的下标分别为 2*i + 12*i + 2,而其父结点的下标为 (i - 1) // 2(向下取整)。这种固定的下标关系使得在数组中进行堆的操作(如插入、删除、调整等)变得非常直观和高效。

  • 空间利用率

由于堆是完全或近乎完全二叉树,其存储在数组中时空间利用率较高。即使不是严格的完全二叉树,只要整体结构相对平衡,数组中的空闲位置也相对较少,不会造成过多的存储浪费。

  • 操作复杂度

堆的常见操作(如插入、删除堆顶元素、调整堆等)的时间复杂度通常为 O(log n),这是因为堆的高度与结点数成对数关系。数组的随机访问特性使得这些操作能够在常数时间内定位到相关结点,然后通过递归或迭代方式进行堆结构调整。

因此,堆作为一类满足特定条件的二叉树,其完全二叉树特性、堆序性质以及高效的操作性能,使其非常适合采用数组进行顺序存储。堆常用于实现优先队列、求解Top-K问题、堆排序算法等场景。

堆的分类

大根堆

大根堆是一种特殊的二叉堆,其中每个节点的值都大于或等于其子节点的值。具体地说,对于大根堆中的任意节点 i,其值 A[i] 大于等于其左孩子 A[2*i+1] 和右孩子A[2*i+2]的值。根节点(数组下标为1或0,取决于实现)总是包含堆中的最大值。大根堆常用于实现优先队列,其中队首元素始终为当前最大的元素。
在这里插入图片描述

小根堆

小根堆也是一种特殊的二叉堆,其中每个节点的值都小于或等于其子节点的值。对于小根堆中的任意节点 i,其值 A[i] 小于等于其左孩子 A[2*i+1] 和右孩子 A[2*i+2] 的值。根节点(同样为数组下标为1或0)始终包含堆中的最小值。小根堆同样适用于优先队列的场景,但此时队首元素为当前最小的元素。
在这里插入图片描述

整体结构把握

我们这里用vector作为底层容器来存储数据,这些数据按照顺序排放:

#pragma once
#include<iostream>
#include<vector>template<class T>
//堆的定义
class Heap
{
public:Heap():_size(0){_data.resize(10);}Heap(const size_t& size):_size(0){_data.resize(size + 1);}//插入void insert(const T& data){if(_size > _data.capacity()){_data.resize(2 * _data.size());}_data[++_size] = data;}//是否为空bool empty(){return _size == 0;}//打印堆void printHeap(){for(int i = 1; i < _size + 1; i++){std::cout<< _data[i] << " ";}std::cout << std::endl;}private:std::vector<T> _data; //存放数据size_t _size; //当前数据个数
};

这里注意一下,我的一个数据并没有放在0号位置,而是放在了1号位置,这样方便我们寻找父节点:
在这里插入图片描述
我们可以先测试一下:

#include"heap.h"int main()
{Heap<int> heap;heap.insert(12);heap.insert(23);heap.insert(1);heap.insert(0);heap.insert(24);heap.insert(4);heap.insert(188);heap.insert(9);heap.insert(58);heap.printHeap();return 0;
}

在这里插入图片描述

两种调整算法

向上调整算法

向上调整算法的核心是把每一个结点都当做孩子,去跟自己的父亲比较,如果比自己的父亲大(或者小)交交换数据
在这里插入图片描述

递归版本

递归版本比较好想,我只管我自己和父亲的比较,比较完之后,继续向上比较:

// 向上调整函数(以小根堆为例)
// 输入参数:index - 需要进行调整的子节点索引
void sifUpHeap(const size_t& index)
{// 如果索引小于 1,说明已经到达根节点或无效索引,无需继续调整,直接返回if (index < 1){return;}// 获取当前子节点的父节点索引size_t parentIndex = Parent(index);// 如果子节点索引大于 1(即不是根节点),并且子节点的值小于其父节点的值// 则交换两者,确保父节点的值小于其子节点的值(小根堆性质)if (index > 1 && _data[parentIndex] > _data[index]){std::swap(_data[parentIndex], _data[index]);// 对交换后的新父节点(原子节点)继续进行向上调整,确保整棵子树满足小根堆性质sifUpHeap(parentIndex);}// 返回,完成当前节点的向上调整过程return;
}

但是我们这样只是完成了一个数据的调整,我们要所有的数据进行调整:

    //向上调整算法(以小根堆为例)void sifUpHeap(const size_t& index){if (index < 1){return;}if(index > 1 && _data[Parent(index)] > _data[index]){std::swap(_data[Parent(index)],_data[index]);//接着向上sifUpHeap(Parent(index));}return;}//调整为小根堆void ToMinHeap(){for(int i = 1; i < _size + 1; i++){sifUpHeap(i);}}

在这里插入图片描述

非递归版本

我们也可以不用递归,使用迭代来完成:

// 向上调整函数(非递归版本,以小根堆为例)
// 输入参数:child - 需要进行调整的子节点索引
void sifUpHeap_non(size_t child)
{// 计算当前子节点的父节点索引size_t parent = child / 2;// 循环迭代,直到子节点成为根节点或已满足小根堆性质while (child > 1){// 如果子节点的值小于其父节点的值// 则交换两者,确保父节点的值小于其子节点的值(小根堆性质)if (_data[Parent(child)] > _data[child]){std::swap(_data[Parent(child)], _data[child]);// 更新子节点索引为交换后的父节点索引,准备对新的子节点进行下一轮比较child = parent;// 重新计算父节点索引parent = child / 2;}else{// 子节点已满足小根堆性质,跳出循环,结束调整break;}}
}//调整为小根堆void ToMinHeap(){for(int i = 1; i < _size + 1; i++){sifUpHeap_non(i);}}

向下调整算法

向下调整算法是把所有结点当做父亲结点,去和自己的孩子结点比较,看哪个孩子结点比自己大或小,就交换
在这里插入图片描述向下调整算法有个条件:左右子树必须为堆,因为这个特性,我们向下调整算法得从最后一个有孩子的双亲结点开始:

// 下降调整函数(以小根堆为例)
// 输入参数:index - 需要进行调整的父节点索引
void sifDownHeap(const size_t& index)
{// 计算当前父节点的左孩子索引size_t leftchild = LeftChild(index);// 如果左孩子索引超出了堆的有效范围(即不存在左孩子),说明无需调整,直接返回if (leftchild > _size){return; // 超出范围,无需调整}// 初始化 "miner" 为当前父节点的左孩子索引// "miner" 用于记录待调整子节点中值最小的那个的索引int miner = leftchild;// 比较左孩子与右孩子(如果存在)的值,确定哪个子节点的值更小// 如果右孩子存在且其值小于左孩子,更新 "miner" 为右孩子索引if (index < _size + 1 && _data[leftchild] > _data[leftchild + 1]){miner++;}// 如果当前父节点的值大于其最小子节点(即 "miner" 所指向的子节点)的值// 则交换两者,确保父节点的值小于其子节点的值(小根堆性质)if (_data[miner] < _data[index]){std::swap(_data[miner], _data[index]);// 对交换后的新父节点(原子节点)继续进行向下调整,确保整棵子树满足小根堆性质sifDownHeap(miner);}// 返回,完成当前节点的向下调整过程return;
}//调整为小根堆void ToMinHeap(){// for(int i = 1; i < _size + 1; i++)// {//     sifUpHeap_non(i);// }for(int i = _size / 2 ; i >=1 ; i--) //从最后一个父节点结点开始调整{sifDownHeap(i);}}

在这里插入图片描述

非递归版本

我们也可以用非递归的方式实现:

// 下降调整函数(以小根堆为例)
// 输入参数:parent - 需要进行调整的父节点索引
void sifDownHeap_non(size_t parent)
{// 计算当前父节点的左孩子索引,假定左孩子为待调整子节点中值最小的一个int child = LeftChild(parent);// 循环迭代,直到越界while (child < _size + 1){// 如果右孩子存在且其值小于左孩子,更新 "child" 为右孩子索引// 保持 "child" 指向待调整子节点中值最小的那个if (parent + 1 < _size + 1 && _data[child] > _data[child + 1]){child++;}// 如果当前父节点的值大于其最小子节点(即 "child" 所指向的子节点)的值// 则交换两者,确保父节点的值小于其子节点的值(小根堆性质)if (_data[parent] > _data[child]){std::swap(_data[parent], _data[child]);// 更新父节点索引为交换后的子节点索引,准备对新的子节点进行下一轮比较parent = child;// 重新计算子节点索引,从新的父节点开始child = LeftChild(parent);}else{// 子节点已满足小根堆性质,跳出循环,结束调整break;}}
}//调整为小根堆void ToMinHeap(){// for(int i = 1; i < _size + 1; i++)// {//     sifUpHeap(i);// }for(int i = _size / 2 ; i >=1 ; i--) //从最后一个结点开始调整{sifDownHeap_non(i);}}

在这里插入图片描述

向上调整算法和向下调整算法的比较

向上调整算法(Sift Up)和向下调整算法(Sift Down)是堆数据结构中常用的两种调整方法,它们各有特点和适用场景,无法简单地说哪个更优秀。选择使用哪种调整方法取决于具体的堆操作需求和上下文。下面分别介绍两者的特性及适用场景:

向上调整算法(Sift Up)

  • 用途:通常用于将新插入的元素或被修改的元素调整到堆中的正确位置,使其满足堆性质(大根堆或小根堆)。例如,在插入新元素后,将其放在堆末尾,然后从该位置开始向上调整,确保新元素及其路径上的所有节点满足堆性质。
  • 特点:从堆底部(新元素所在位置或被修改元素所在位置)开始,逐层向上比较父节点与子节点的值,若子节点值更适合堆顶(对于大根堆,子节点值更大;对于小根堆,子节点值更小),则交换二者,直至子节点成为堆顶或已满足堆性质。
  • 优点
  • 适用于插入操作和单元素修改后的调整,因为新元素或被修改元素的初始位置已知,可以直接从该位置开始调整。
  • 调整过程中涉及的节点数量相对较少,时间复杂度为 O(log n),效率较高。
  • 缺点
  • 不适用于堆顶元素被删除后的调整,因为此时需要重新确定堆顶元素,且可能需要对多个子节点进行比较和调整。

向下调整算法(Sift Down)

  • 用途:通常用于删除堆顶元素后重新调整堆,或在构建堆的过程中对整个堆进行调整。例如,在删除堆顶元素后,将堆末尾元素移至堆顶,然后从堆顶开始向下调整,确保所有节点满足堆性质。
  • 特点:从堆顶开始,逐层向下比较父节点与子节点的值,若父节点值更适合堆底(对于大根堆,父节点值更小;对于小根堆,父节点值更大),则交换二者,直至父节点成为堆底或已满足堆性质。
  • 优点
  • 适用于堆顶元素被删除后的调整,因为此时堆顶元素已知,可以直接从该位置开始调整。
  • 在构建堆的过程中,可以从最后一个非叶子节点开始逐个进行向下调整,确保整个堆满足堆性质。
  • 缺点
  • 对于插入操作或单元素修改后的调整,可能需要遍历到堆底才能找到新元素或被修改元素的最终位置,调整过程中涉及的节点数量可能较多。

综上所述,向上调整算法和向下调整算法各有优势,适用于不同的堆操作场景。在实际应用中,根据具体需求选择合适的调整方法,或者结合使用这两种方法,可以有效维护堆数据结构的性质,确保堆操作的高效性。因此,不能简单地说哪个更优秀,而应视具体情况灵活选用。

如果大家阅读完之后还是比较迷糊的话,可以点击这里,这里是我之前写的堆的博客,介绍的更为详细:

https://blog.csdn.net/qq_67693066/article/details/131544172


http://www.mrgr.cn/p/38115803

相关文章

Acrobat Pro DC 2021:强大的PDF编辑软件

Acrobat Pro DC 2021是Adobe公司推出的一款全面而强大的PDF编辑软件&#xff0c;凭借其卓越的性能和丰富的功能&#xff0c;成为了PDF编辑领域的领军者。 Acrobat Pro DC 2021中文激活版下载 这款软件具备全面的PDF编辑功能&#xff0c;包括文本编辑、图片处理、页面组织等&…

XMU《计算方法》实验三 龙贝格算法

实验三 龙贝格算法 实验报告 一、代码 clear; fun = inline(input(请输入函数:f(x) = , s)); a = input(请输入下界 a = ); b = input(请输入上界 b = ); e = input(请输入误差限 e = );h = b - a; k = 1; N = 1; T(1, 1) = h / 2 * (fun(a) + fun(b)); while truesum = 0;for…

半导体机台文件导出,如何实现统一管理减轻运维压力?

半导体机台在半导体制造过程中会产生多种数据,这些数据对于设备的运行、监控、优化和故障诊断等方面都具有重要意义。以下是半导体机台可能产生的一些主要数据类型: 工艺控制数据:这包括在制造过程中的各个工艺步骤(如光刻、蚀刻、沉积等)中产生的数据。这些数据反映了制…

分享一些实用的工具

1、amCharts5&#xff1a;模拟航线飞行/业务分布图/k线/数据分析/地图等 网址&#xff1a; JavaScript mapping library: amCharts 5https://www.amcharts.com/javascript-maps/ Demo地址&#xff1a;Chart Demos - amChartshttps://www.amcharts.com/demos/#maps 他分为amC…

XMU《计算方法》实验二 FFT

实验二 FFT 韩可可 22920212204379 一、MATLAB 代码 clear;N = 32; TIME = 5;X = linspace(-pi, pi, 33); X = X(1 : 32); A = X .^ 2 .* cos(X);for m = 0 : N-1w(m+1) = exp(1i * 2 * pi / 32 * m); endi = 1; while i < NB = A;for j = 0 : i*2 : N-1for k = 0 : i-1A(j …

鸿蒙OpenHarmony【小型系统 编译】(基于Hi3516开发板)

编译 OpenHarmony支持hb和build.sh两种编译方式。此处介绍hb方式&#xff0c;build.sh脚本编译方式请参考[使用build.sh脚本编译源码]。 使用build.sh脚本编译源码 进入源码根目录&#xff0c;执行如下命令进行版本编译。 ./build.sh --product-name name --ccache 说明&…

【Jenkins】持续集成与交付 (一):深入理解什么是持续集成?

【Jenkins】持续集成与交付 (一):深入理解什么是持续集成? 1、软件开发生命周期与持续集成2、 持续集成的流程3、持续集成的好处4、Jenkins的应用实践5、结语💖The Begin💖点点关注,收藏不迷路💖 1、软件开发生命周期与持续集成 软件开发生命周期(SDLC)是指软件从…

短视频app制作,实现数据隔离可选方案有很多

一、JDBC数据源隔离在短视频app制作中,数据隔离需要对DB,Redis,RabbitMQ进行数据隔离通过实现Spring动态数据源AbstractRoutingDataSource,通过ThreadLocal识别出来压测数据,如果是压测数据就路由到影子库,如果是正常流量则路由到主库,通过流量识别的改造,各个服务都已经…

清华出品的AI学术助手--清AI

作为一款PC端的AI工具,这款软件的安装非常简单,有一个在线安装程序,大小只有2kb, 但是运行该.application扩展名程序后会在线下载256MB的软件包,下载完即可直接使用。软件支持Windows10及以上操作系统,并且支持自动升级。 安装完成后需要用手机号注册并登录才能使用,登录…

地图数据采集,包括百度地图采集,高德地图采集,360地图采集

地图数据 采集,包括百度地图采集,高德地图采集,360地图采集,支持TXT和Excel格式导出,实时采集,非历史数据,而是官网当前最新的POI数据。地图数据 采集,包括百度地图采集,高德地图采集,360地图采集,支持TXT和Excel格式导出,实时采集,非历史数据,而是官网当前最新的…

加速博客体验:静态资源优化技巧大揭秘!

在博客写作中,静态资源加速方式是解决图片加载速度的关键。通过图片压缩、Nginx静态资源缓存和EdgeOne等CDN产品的应用,我们提高了网站性能和用户体验,减轻了服务器带宽压力。EdgeOne的功能包括静态加速、网站证书和HTTP重定向至HTTPS,简化了网站管理流程,提升了安全性。合…

【Leetcode每日一题】 分治 - 面试题 17.14. 最小K个数(难度⭐⭐)(66)

1. 题目解析 题目链接&#xff1a;面试题 17.14. 最小K个数 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 在快速排序算法中&#xff0c;我们通常会通过选择一个基准元素&#xff0c;然后将数组划分为三个部分&…

XMU《UNIX 系统程序设计》第一次实验报告 (同步与异步 write 的效率比较)

实验一 同步与异步 write 的效率比较 一、实验内容描述 编写程序,比较在同步和异步的条件下 write 系统调用的效率差异。 程序要求从 stdin 读入数据,输出到以命令参数给出的文件中,并以列表的格式给出输出消耗的时间。1、编写程序 timewrite <outfile> [sync]不得…

Jammy@Jetson Orin - Tensorflow Keras Get Started: Concept

JammyJetson Orin - Tensorflow & Keras Get Started: Concept 1. 源由2. 模型2.1 推理流程2.1.1 获取图像2.1.2 算法识别2.1.3 判断决策 2.2 理想情况2.2.1 多因素输入2.2.2 理想识别概率 2.3 学习过程2.3.1 标记训练集2.3.2 损失函数2.3.3 训练网络2.3.4 渐进方法 3. 总…

XMU《计算机网络与通信》第五次实验报告

实验五 运输层与应用层协议分析 目录实验五 运输层与应用层协议分析一、个人信息二、实验目的三、实验内容、步骤与结果任务一 TCP 正常连接观察任务二 异常传输观察分析1. 尝试连接未存活的主机或对未监听端口。(a) 用 curl 访问一个不存在的主机 IP,抓包观察共发送了几次 SY…

短视频开发,如何用Java实现短信验证码?

背景Java是一种短视频开发时比较流行的编程语言,验证码是一种常用的网络安全技术。Java发展至今,网上也出现了各种各样的验证码,本人初学Java,下面是我用Java实现短信验证码的总结。截图展示实现代码短视频开发后台接收前台的kgCaptchaToken进行验证,验证成功执行成功处理…

MFC实现ini配置文件的读取

MFC实现 ini 配置文件的读取1 实现的功能&#xff1a;点击导入配置文件按钮可以在旁边编辑框中显示配置文件的路径&#xff0c;以及在下面的编辑框中显示配置文件的内容。 1. 显示配置文件内容的编辑框设置 对于显示配置文件内容的 Edit Contorl 编辑框的属性设置如下&#x…

XMU《计算机网络与通信》第四次实验报告

计算机网络 实验四 通信 这次实验的话,我的报告参考意义不大,毕竟这次实验的主要难点是完成那两个代码,但是我报告中没有任何对于代码的解释。 大家如果需要的话,我的两个代码可以在这里下载,仅供参考:点击下载。 一、个人信息 姓名:XXX 学号:XXXXXXXXXXXXXX 二、实验目…

XMU《计算机网络与通信》第二次实验报告

实验二 实验报告 一、个人信息 姓名:XXX 学号:XXXXXXXXXXXXXX 二、实验目的学习捕获和分析网络数据包掌握以太网MAC帧、802.11数据帧和IPv4数据包的构成,了解各字段的含义掌握ICMP协议,ping和tracert指令的工作原理掌握ARP协议的请求/响应机理三、实验内容与结果分析 任务 …

[Python急救站]人脸识别技术练习

这段时间做了一个用于初学者学习人脸识别系统的程序,在上代码时,先给说说事前准备: 首先我们需要一个OpenCV的一个haarcascade_frontalface_default.xml文件,只要去GitHub上面即可下载:https://github.com/opencv/opencv 点击Code,选择Download ZIP,下载后解压在目录下o…