数据结构--二叉树的顺序实现(堆实现)

引言

在计算机科学中,二叉树是一种重要的数据结构,广泛应用于各种算法和程序设计中。本文将探讨二叉树的顺序实现,特别是堆的实现方式。

一、树

1.1树的概念与结构

树是⼀种⾮线性的数据结构,它是由 n(n>=0)  个有限结点组成⼀个具有层次关系的集合。把它叫做
树是因为它看起来像⼀棵倒挂的树,也就是说它是根朝上,⽽叶朝下的。
有⼀个特殊的结点,称为根结点,根结点没有前驱结点。
除根结点外,其余结点被分成 M(M>0) 个互不相交的集合 T1、T2、……、Tm ,其中每⼀个集合 Ti(1 <= i <= m) ⼜是⼀棵结构与树类似的⼦树。每棵⼦树的根结点有且只有⼀个前驱,可以有 0 个或多个后继。因此,树是递归定义的。

树形结构中,⼦树之间不能有交集,否则就不是树形结构
⾮树形结构:

总结:

⼦树是不相交的(如果存在相交就是图了,图以后得课程会有讲解)
除了根结点外,每个结点有且仅有⼀个⽗结点
⼀棵N个结点的树有N-1条边

1.2树的基本术语 

  • ⽗结点/双亲结点:若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点; 如上图:A是B的⽗结点

  • ⼦结点/孩⼦结点:⼀个结点含有的⼦树的根结点称为该结点的⼦结点; 如上图:B是A的孩⼦结点

  • 结点的度:⼀个结点有⼏个孩⼦,他的度就是多少;⽐如A的度为6,F的度为2,K的度为0

  • 树的度:⼀棵树中,最⼤的结点的度称为树的度; 如上图:树的度为 6

  • 叶⼦结点/终端结点:度为 0 的结点称为叶结点; 如上图: BCHI... 等结点为叶结点

  • 分⽀结点/⾮终端结点:度不为 0 的结点; 如上图: DEFG... 等结点为分⽀结点

  • 兄弟结点:具有相同⽗结点的结点互称为兄弟结点(亲兄弟); 如上图: BC 是兄弟结点

  • 结点的层次:从根开始定义起,根为第 1 层,根的⼦结点为第 2 层,以此类推;

  • 树的⾼度或深度:树中结点的最⼤层次; 如上图:树的⾼度为 4

  • 结点的祖先:从根到该结点所经分⽀上的所有结点;如上图: A 是所有结点的祖先

  • 路径:⼀条从树中任意节点出发,沿⽗节点-⼦节点连接,达到任意节点的序列;⽐如A到Q的路径为: A-E-J-Q;H到Q的路径H-D-A-E-J-Q

  • ⼦孙:以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙。如上图:所有结点都是A的⼦孙

  • 森林:由 m(m>0)棵互不相交的树的集合称为森林;

1.3树的表示法

孩⼦兄弟表⽰法:
树结构相对线性表就⽐较复杂了,要存储表⽰起来就⽐较⿇烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表⽰⽅式如:双亲表⽰法,孩⼦表⽰法、孩⼦双亲表⽰法以及孩⼦兄弟表⽰法等。我们这⾥就简单的了解其中最常⽤的孩⼦兄弟表⽰法
struct TreeNode{struct Node* child; 	// 最左边的孩子结点struct Node* brother; 	// 指向其右边的下一个兄弟结点int data; 				// 结点中的数据域
};

1.4树形结构实际运⽤场景

⽂件系统是计算机存储和管理⽂件的⼀种⽅式,它利⽤树形结构来组织和管理⽂件和⽂件夹。在⽂件系统中,树结构被⼴泛应⽤,它通过⽗结点和⼦结点之间的关系来表⽰不同层级的⽂件和⽂件夹之间的关联。

二、二叉树 

2.1二叉树的概念与结构

二叉树(binary tree) 是一种非线性数据结构,代表“祖先”与“后代”之间的派生关系,体现了“一分为二” 的分治逻辑。与链表类似,二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子节点引用。

总结:
1. ⼆叉树不存在度⼤于 2 的结点
2. ⼆叉树的⼦树有左右之分,次序不能颠倒,因此⼆叉树是有序树
注意:对于任意的⼆叉树都是由以下⼏种情况复合⽽成的

2.2二叉树的基本术语

  • 根节点(root node) :位于二叉树顶层的节点,没有父节点。
  • 叶节点(leaf node):没有子节点的节点,其两个指针均指向 None
  • 节点所在的 层(level) :从顶至底递增,根节点所在层为 1 。
  • 节点的度(degree) :节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。
  • 二叉树的高度(height) :从根节点到最远叶节点所经过的边的数量。
  • 节点的深度(depth) :从根节点到该节点所经过的边的数量。
  • 节点的高度(height) :从距离该节点最远的叶节点到该节点所经过的边的数量。

 2.3特殊二叉树

1.完美二叉树(满二叉树)

完美二叉树(perfect binary tree) 所有层的节点都被完全填满。在完美二叉树中,叶节点的度
0 ,其余所有节点的度都为 2 ;若树的高度为 ,则节点总数为 2 ℎ+1 − 1 ,呈现标准的指数级关系,反映了自然界中常见的细胞分裂现象。

2. 完全二叉树
完全二叉树(complete binary tree) 只有最底层的节点未被填满,且最底层节点尽量靠左填充。

2.4二叉树的存储结构

⼆叉树⼀般可以使⽤两种结构存储,⼀种顺序结构,⼀种链式结构。

2.4.1顺序结构

顺序结构存储就是使⽤数组来存储,⼀般使⽤数组只适合表⽰完全⼆叉树 ,因为不是完全⼆叉树会有空间的浪费,完全⼆叉树更适合使⽤顺序结构存储。

2.3.2 链式结构

⼆叉树的链式存储结构是指,⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。 通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址 。链式结构⼜分为 ⼆叉链 三叉链 。(红⿊树等会⽤到三叉链。)

三、实现顺序结构二叉树 

⼀般堆使⽤顺序结构的数组来存储数据,堆是⼀种特殊的⼆叉树,具有⼆叉树的特性的同时,还具备其他的特性。

3.1堆的概念与结构

堆是一种完全二叉树,具有以下性质:

  • 最大堆:每个节点的值都大于或等于其子节点的值。

  • 最小堆:每个节点的值都小于或等于其子节点的值。

堆常用于实现优先队列,因为它能有效地支持插入和删除操作。

总结
堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值;
堆总是⼀棵完全⼆叉树。

3.2堆的实现

我们以小堆为例进行实现:

3.2.1存储结构

二叉树的顺序存储通常使用数组来实现。对于一个节点在数组中的索引 i,可以通过以下方式找到其父节点和子节点的索引:

  • 父节点(i - 1) / 2(取整)
  • 左子节点2 * i + 1
  • 右子节点2 * i + 2
typedef struct Heap
{DataType *arr;int size;int capacity;
}HP;

这里可以看到堆的底层结构和动态顺序表一样。

3.2.2相关操作

1.初始化

//初始化
void HPInit(HP* p)
{assert(p);p->arr = NULL;p->size = p->capacity = 0;
}

2.销毁 

//销毁
void HPDestory(HP* p)
{assert(p);if (p->arr){free(p->arr);}p->arr = NULL;p->size = p->capacity = 0;
}
Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}

3.向上调整

💡 向上调整算法
先将元素插⼊到堆的末尾,即最后⼀个孩⼦之后
插⼊之后如果堆的性质遭到破坏,将新插⼊结点顺着其双双亲往上调整到合适位置即可

//堆的向上调整
void AdjustUp(DataType* arr, int child)
{int parent = (child - 1) / 2;//父节点while(child>0)//注意child可能会被调整到根节点,这时候就不能再调整了{if (arr[child] < arr[parent])//如果条件语句不成立,就说明堆已经成型{Swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;//循环以上步骤}else{break;}}
}

4.堆的插⼊

将新数据插⼊到数组的尾上,再进⾏向上调整算法,直到满⾜堆。

//插入
void HPPush(HP* p, DataType x)
{assert(p);if (p->size == p->capacity)//判断空间是否足够{//扩容int NewCap = p->capacity == 0 ? 4 : 2 * p->capacity;DataType* tmp = (DataType*)realloc(p->arr, NewCap * sizeof(DataType));if (tmp == NULL) {perror("realloc fail!");exit(1);}p->arr = tmp;p->capacity = NewCap;} p->arr[p->size] = x;AdjustUp(p->arr, p->size);++p->size;
}

5.向下调整法

💡 向下调整算法
将堆顶元素与堆中最后⼀个元素进⾏交换
删除堆中最后⼀个元素
将堆顶元素向下调整到满⾜堆特性为⽌
向下调整算法有⼀个前提:左右⼦树必须是⼀个堆,才能调整。

//堆的向下调整
void AdjustDwon(DataType* arr, int parent, int n)
{int child = parent * 2 + 1;//左孩子while (child < n) {//寻找左右孩子最小的那个if (child + 1 < n && arr[child] > arr[child + 1]){child++;}//这里和向上调整就一样了,比较,交换,循环if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

 6.堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后⼀个数据⼀换,然后删除数组最后⼀个数据,再进⾏向下调整算法。

//删除,出堆顶数据
void HPPop(HP* p)
{assert(p && p->size);Swap(&p->arr[0], &p->arr[p->size - 1]);--p->size;AdjustDwon(p->arr, 0, p->size);
}

7.判空

//判空
bool HPEmpty(HP* p)
{assert(p);return p->size == 0;
}

8.返回堆顶元素

//返回堆顶元素
DataType HPTop(HP* p)
{assert(p && p->size);return p->arr[0];
}

四、完整代码

Heap.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
//定义堆的结构--数组
typedef int DataType;
typedef struct Heap//小堆为例
{DataType *arr;int size;int capacity;
}HP;
//初始化
void HPInit(HP* p);
//销毁
void HPDestory(HP* p);
//插入
void HPPush(HP* p,DataType x);
//删除,出堆顶数据
void HPPop(HP* p);
//判空
bool HPEmpty(HP* p);
//返回堆顶元素
DataType HPTop(HP* p);

Heap.c

#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"
//初始化
void HPInit(HP* p)
{assert(p);p->arr = NULL;p->size = p->capacity = 0;
}
//销毁
void HPDestory(HP* p)
{assert(p);if (p->arr){free(p->arr);}p->arr = NULL;p->size = p->capacity = 0;
}
Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}
//堆的向上调整
void AdjustUp(DataType* arr, int child)
{int parent = (child - 1) / 2;//父节点while(child>0)//注意child可能会被调整到根节点,这时候就不能再调整了{if (arr[child] < arr[parent])//如果条件语句不成立,就说明堆已经成型{Swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;//循环以上步骤}else{break;}}
}
//插入
void HPPush(HP* p, DataType x)
{assert(p);if (p->size == p->capacity)//判断空间是否足够{//扩容int NewCap = p->capacity == 0 ? 4 : 2 * p->capacity;DataType* tmp = (DataType*)realloc(p->arr, NewCap * sizeof(DataType));if (tmp == NULL) {perror("realloc fail!");exit(1);}p->arr = tmp;p->capacity = NewCap;} p->arr[p->size] = x;AdjustUp(p->arr, p->size);++p->size;
}
//堆的向下调整
void AdjustDwon(DataType* arr, int parent, int n)
{int child = parent * 2 + 1;//左孩子while (child < n) {//寻找左右孩子最小的那个if (child + 1 < n && arr[child] > arr[child + 1]){child++;}//这里和向上调整就一样了,比较,交换,循环if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
//删除,出堆顶数据
void HPPop(HP* p)
{assert(p && p->size);Swap(&p->arr[0], &p->arr[p->size - 1]);--p->size;AdjustDwon(p->arr, 0, p->size);
}
//判空
bool HPEmpty(HP* p)
{assert(p);return p->size == 0;
}
//返回堆顶元素
DataType HPTop(HP* p)
{assert(p && p->size);return p->arr[0];
}

main.c

#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"
void test01()
{HP hp;HPInit(&hp);int arr[] = { 17,20,10,13,19,15 };for (int i = 0; i < 6; i++){HPPush(&hp, arr[i]);}//HPPop(&hp);while (!HPEmpty(&hp)){printf("%d-", HPTop(&hp));HPPop(&hp);}HPDestory(&hp);
}
int main()
{test01();return 0;
}

五、总结

通过顺序实现的方式,我们可以高效地存储和操作二叉树。堆作为一种特殊的二叉树,提供了快速的插入和删除操作,非常适合优先队列的实现。在许多应用场景中,如任务调度、图算法等,堆结构都发挥着重要作用。

希望本文能够帮助你理解二叉树的顺序实现及堆的基本操作。继续探索数据结构的世界,会发现更多有趣的应用和挑战!

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

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

相关文章

【HTML5】html5开篇基础(5)

1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; Hello, Hello~ 亲爱的朋友们&#x1f44b;&#x1f44b;&#xff0c;这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章&#xff0c;请别吝啬你的点赞❤️❤️和收藏&#x1f4d6;&#x1f4d6;。如果你对我的…

vue-live2d看板娘集成方案设计使用教程

文章目录 前言v1.1.x版本&#xff1a;vue集成看板娘&#xff08;暂不使用&#xff0c;在v1.2.x已替换&#xff09;集成看板娘实现看板娘拖拽效果方案资源备份存储 当前最新调研&#xff1a;2024.10.2开源方案1&#xff1a;OhMyLive2D&#xff08;推荐&#xff09;开源方案2&…

【设计模式】软件设计原则——接口隔离迪米特

接口隔离原则引出 接口隔离原则 定义&#xff1a;用多个专门的接口,不使用单一的总接口,客户端不应该依赖它不需要的接口; 一个类对另一个类的依赖,应该建立在最小接口上;如果有一个大接口,里面有很多方法,如果使用一个类实现该接口,所有的类都要实现&#xff0c;导致代码冗余;…

android 全面屏最底部栏沉浸式

Activity的onCreate方法中添加 this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); Android 系统 Bar 沉浸式完美兼容方案自 Android 5.0 版本&#xff0c;Android 带来了沉浸式系统 ba - 掘金 (juejin.cn)https://juejin.cn/post/7075578…

【HTTP(3)】(状态码,https)

【认识状态码】 状态码最重要的目的&#xff0c;就是反馈给浏览器:这次请求是否成功&#xff0c;若失败&#xff0c;则出现失败原因 常见状态码: 200:OK&#xff0c;表示成功 404:Not Found&#xff0c;浏览器访问的资源在服务器上没有找到 403:Forbidden&#xff0c;访问被…

【每天学个新注解】Day 15 Lombok注解简解(十四)—@UtilityClass、@Helper

UtilityClass 生成工具类的注解 将一个类通过注解变成一个工具类&#xff0c;并没有什么用&#xff0c;本来代码中的工具类数量就极为有限&#xff0c;并不能达到减少重复代码的目的 1、如何使用 加在需要委托将其变为工具类的普通类上。 2、代码示例 例&#xff1a; Uti…

基于Java,SpringBoot,Vue智慧校园健康驿站体检论坛请假管理系统

摘要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差&#xf…

景区+商业,如何实现1+1>2?

景区商业&#xff0c;如何实现11&#xff1e;2&#xff1f; 近两年&#xff0c;随着旅游业的蓬勃发展&#xff0c;旅游热潮持续升温&#xff0c;游客的消费观念也在逐步升级。为了适应这一趋势&#xff0c;各大景区纷纷着手打造具有鲜明特色的文旅项目&#xff0c;希望能够吸引…

C++ | Leetcode C++题解之第457题环形数组是否存在循环

题目&#xff1a; 题解&#xff1a; class Solution { public:bool circularArrayLoop(vector<int>& nums) {int n nums.size();auto next [&](int cur) {return ((cur nums[cur]) % n n) % n; // 保证返回值在 [0,n) 中};for (int i 0; i < n; i) {if …

cherry-markdown开源markdown组件详细使用教程

文章目录 前言开发定位目标调研技术方案前提工作量安排数据库表设计实现步骤1、引入依赖2、实现cherry-markdown的vue组件&#xff08;修改上传接口路径&#xff09;3、支持draw.io组件4、支持展示悬浮目录toc前端使用&#xff1a;编辑状态使用cherry-markdown的vue组件前端使用…

解决npm安装不了element库(目前未解决。。。)

根据您提供的错误信息&#xff0c;安装 element-plus 时出现了一些问题。这些错误主要可以分为两类&#xff1a;权限问题和网络问题。以下是一些解决这些问题的建议&#xff1a; 1. 解决权限问题 您遇到的 EPERM: operation not permitted 错误通常与文件系统权限有关。尝试以…

Stable Diffusion绘画 | 插件-Deforum:动态视频生成(中篇)

本篇文章重点讲解参数最多的 关键帧 模块。 「动画模式」选择「3D」&#xff1a; 下方「运动」Tab 会有一系列参数&#xff1a; 以下4个参数&#xff0c;只有「动画模式」选择「2D」才会生效&#xff0c;可忽略&#xff1a; 运动 平移 X 让镜头左右移动&#xff1a; 大于0&a…

卷积神经网络(CNN)的计算量和参数怎么准确估计?

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 1. 卷积层&#xff08;Convolutional Layer&#xff09; a) 计算量估计&#xff1a; 卷积层的 FLOPs 2 * H_out * W_out * C_in * C_out * K_h * K_w 详细解释&#xff1a; H_out, W_out&#xff…

YOLO11改进|注意力机制篇|引入HAT超分辨率重建模块

目录 一、HAttention注意力机制1.1HAttention注意力介绍1.2HAT核心代码 二、添加HAT注意力机制2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、HAttention注意力机制 1.1HAttention注意力介绍 HAT模型 通过结合卷积特征提取与多尺度注意…

推荐 uniapp 相对好用的海报生成插件

插件地址&#xff1a;自定义canvas样式海报 - DCloud 插件市场 兼容性也是不错的&#xff1a;

MySQL基础篇 - 事务

01 事务的简介 【1】什么是事务&#xff1a;事务是一组操作集合&#xff0c;要么同时操作成功&#xff0c;要么同时操作失败。 【2】对于MySQL数据库来说默认一条SQL语句就是一个事务&#xff0c;且事务是默认自动提交的。 我们可以把多条SQL语句设置成一个事务&#xff0c;使…

java:pdfbox 删除扫描版PDF中文本水印

官网下载 https://pdfbox.apache.org/download.html下载 pdfbox-app-3.0.3.jar cd D:\pdfbox 运行 java -jar pdfbox-app-3.0.3.jar java -jar pdfbox-app-3.0.3.jar Usage: pdfbox [COMMAND] [OPTIONS] Commands:debug Analyzes and inspects the internal structu…

Spring Boot技术栈:打造高效在线商城

2 相关技术 2.1 Springboot框架介绍 Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。通过这种方式&#xff0c;Spring…

重学SpringBoot3-集成Redis(一)

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Redis&#xff08;一&#xff09; 1. 项目初始化2. 配置 Redis3. 配置 Redis 序列化4. 操作 Redis 工具类5. 编写 REST 控制器6. 测试 API7. 总结 随…

vSAN01:vSAN简介、安装、磁盘组、内部架构与调用关系

目录 传统的共享存储vSAN存储OSA的系统要求vSAN安装vSAN集群vSAN skyline healthvSAN与HA磁盘组混合磁盘架构全闪磁盘架构 vSAN对象vSAN内部架构 传统的共享存储 通过隔离的存储网络使得不同的ESXi主机访问独立的存储设备。需要前期投入较高的资金单独采购存储、网络可以单独规…