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

数据结构——二叉树的顺序存储(堆)(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.mzph.cn/news/829648.shtml

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

相关文章

owasp top 10 越权漏洞·

指的是用户权限不明晰。 垂直越权&#xff1a;低权限用户&#xff0c;在无任何身份校验的情况下&#xff0c;能够使用高权 限&#xff08;不属于自己&#xff09;模块&#xff08;功能&#xff09;&#xff0c;或访问高权限拥有的数据&#xff1b; 水平越权&#xff1a;指相同…

用C++实现单例模式

单例模式&#xff08;Singleton Pattern&#xff09;是一种常用的软件设计模式&#xff0c;它保证一个类在整个应用程序中仅有一个实例&#xff0c;并提供一个全局访问点来获取这个实例。这种模式常用于那些需要频繁创建且消耗资源较多的对象&#xff0c;或者需要控制对共享资源…

华为MRS服务使用记录

背景&#xff1a;公司的业务需求是使用华为的这一套成品来进行开发&#xff0c;使用中发现&#xff0c;这个产品跟原生的Hadoop的那一套的使用&#xff0c;还是有很大的区别的&#xff0c;现记录一下&#xff0c;避免以后忘了 一、原始代码的下载 下载地址&#xff1a;MRS样例…

UVa12313 A Tiny Raytracer

UVa12313 A Tiny Raytracer 题目链接题意分析AC 代码 题目链接 UVA - 12313 A Tiny Raytracer 题意 给出 《训练指南》题意翻译 本题的任务是实现一个小型光线追踪渲染器。场景由若干三角形网格&#xff08;triangle mesh&#xff09;组成&#xff0c;有且仅有一个点光源&…

lua编译器和lua解释器、lua虚拟机的区别

一、区别总结 lua编译器&#xff1a; 将lua源代码编译成字节码&#xff0c;提高代码加载速度 lua解释器&#xff1a;逐条执行编译器生成的字节码&#xff0c;并将其转换为虚拟机可以执行的指令。 lua虚拟机&#xff1a;提供了执行指令所需要的环境 二、lua编译器 Lua编译器的主…

R语言高级数据管理

一&#xff0c;数学函数 绝对值函数abs(x) sqrt(x) 开平方根 不小于某个数的最小整数ceiling(x) 不大于某个数的最大整数floor(x) 四舍五入round(x) sin(x) cos(x) log(x) 二&#xff0c;统计函数 求平均值 > x<-c(2,3,4,5,6,7,8,9,10) > mean(x) 求和 &g…

RustGUI学习(iced)之小部件(二):如何使用滑动条部件

前言 本专栏是学习Rust的GUI库iced的合集&#xff0c;将介绍iced涉及的各个小部件分别介绍&#xff0c;最后会汇总为一个总的程序。 iced是RustGUI中比较强大的一个&#xff0c;目前处于发展中&#xff08;即版本可能会改变&#xff09;&#xff0c;本专栏基于版本0.12.1. 概述…

抓包理解协议

用的Wireshark 抓包 1.抓包网卡选择 - WLAN 无线网卡&#xff0c;其他是本地虚拟机的网卡 这里分别是开始捕获、停止捕获、重新捕获、网卡选择&#xff0c;下面是可以过滤选择 过滤tcp包 3次握手&#xff1a; source是源地址&#xff0c; destination是目标地址&#xff0c;in…

QT文件上传带进度条实例(含源码)

一、Qt上传知识点 QNetworkAccessManager QNetworkAccessManager 是 Qt 中用于执行网络操作的类,它提供了一种方便的方式来发送网络请求并处理响应。以下是关于 QNetworkAccessManager 的一些重要知识点: 发送网络请求:QNetworkAccessManager 可以通过不同的方法发送网络请…

Linux: Netlink 简介

文章目录 1. 前言2. Netlink 范例3. Netlink 简析3.1 Netlink 协议簇注册3.2 创建 用户空间 Netlink 套接字3.3 用户空间 Netlink 套接字 的 绑定3.4 向 内核空间 Netlink 套接字 发消息3.5 从 内核空间 Netlink 套接字 读消息3.5.1 内核空间 Netlink 套接字 向 用户空间 Netli…

CSS3(响应式布局)

#过渡# 属性连写&#xff1a; transition: width 2s linear 1s; //前一个时间用于表示过渡效果持续时间&#xff0c;后一个时间用于表示过渡效果的延迟。 #转换# #2D转换# 和 #3D转换# 注意&#xff1a;其中angle对应单位为&#xff1a;deg #圆角# #边框# …

使用Docker部署Jupyter Notebook并结合花生壳的内网穿透实现远程访问(详文)

一、前言 本文主要介绍如何利用宝塔面板中的Docker 3.9.3管理器,使用Docker本地部署Jupyter Notebook,并结合花生壳内网穿透工具实现任意浏览器公网远程访问Jupyter登录界面。 安装完成后在宝塔面板中图例 Jupyter Notebook是一个交互式笔记本,支持运行40多种编程语言。…

会声会影2024破解电脑版下载附带永久免费激活码

会声会影2024是一款功能强大的视频编辑软件&#xff0c;它凭借直观易用的界面、全面的编辑工具以及丰富的特效和转场效果&#xff0c;受到了广大视频编辑爱好者和专业人士的喜爱。以下是对会声会影2024的详细介绍&#xff1a; 会声会影最新一键安装包下载如下&#xff1a; ht…

sqlserver在执行维护计划备份时报错,重新设置维护任务,也同样出错,另一计划的异地备份又正常。

出错提示&#xff1a;已以用户 NT Service\SQLSERVERAGENT 的身份执行。 最大值(1)&#xff0c;因此导致失败。当错误数达到 MaximumErrorCount 中指定的数目时将发生这种情况。请更改 MaximumErrorCount 或纠正这些错误。 警告结束 DTExec: 已返回包执行 DTSER_FAILURE (1)。…

web页面点击右键显示按钮

首先声明一个对象&#xff0c;然后把声明的对象&#xff0c;赋值一个function&#xff0c;在对象的function当中再return一个function&#xff0c;在返回的这个function中第一步就是要把按钮的class先移除&#xff0c;不然到后面取消右键显示按钮的时候会失效&#xff0c;按钮依…

费曼学习法个人总结-1

2024年4月27日 1&#xff0c;知道它叫什么和懂得它是什么是两件事&#xff0c;要了解事物的本质&#xff0c;不应该只是了解名字。 2&#xff0c;输出倒逼输入。 3&#xff0c;实施的5步骤 调整心态&#xff0c;开始行动指定目标&#xff0c;养成习惯找对方法&#xff0c;高…

PotatoPie 4.0 实验教程(23) —— FPGA实现摄像头图像伽马(Gamma)变换

为什么要进行Gamma校正 图像的 gamma 校正是一种图像处理技术&#xff0c;用于调整图像的亮度和对比度&#xff0c;让显示设备显示的亮度和对比度更符合人眼的感知。Gamma 校正主要用于修正显示设备的非线性响应&#xff0c;以及在图像处理中进行色彩校正和图像增强。 以前&am…

《从零开始的Java世界》11网络编程

《从零开始的Java世界》系列主要讲解Javase部分&#xff0c;从最简单的程序设计到面向对象编程&#xff0c;再到异常处理、常用API的使用&#xff0c;最后到注解、反射&#xff0c;涵盖Java基础所需的所有知识点。学习者应该从学会如何使用&#xff0c;到知道其实现原理全方位式…

LangChain入门:24.通过Baby AGI实现自动生成和执行任务

随着 ChatGPT 的崭露头角,我们迎来了一种新型的代理——Autonomous Agents(自治代理或自主代理)。 这些代理的设计初衷就是能够独立地执行任务,并持续地追求长期目标。 在 LangChain 的代理、工具和记忆这些组件的支持下,它们能够在无需外部干预的情况下自主运行,这在真…

冯唐成事心法笔记 —— 知己

系列文章目录 冯唐成事心法笔记 —— 知己 冯唐成事心法笔记 —— 知人 冯唐成事心法笔记 —— 知世 冯唐成事心法笔记 —— 知智慧 文章目录 系列文章目录卷首语 管理是一生的日常&#xff0c;成事是一生的修行PART 1 知己 用好自己的天赋如何管理自我第一&#xff0c;如何…