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

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

目录

引言

一、树

1.1树的概念与结构

1.2树的基本术语 

1.3树的表示法

1.4树形结构实际运⽤场景

二、二叉树 

2.1二叉树的概念与结构

2.2二叉树的基本术语

 2.3特殊二叉树

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

2.4二叉树的存储结构

2.4.1顺序结构

2.3.2 链式结构

三、实现顺序结构二叉树 

3.1堆的概念与结构

3.2堆的实现

3.2.1存储结构

3.2.2相关操作

四、完整代码

Heap.h

Heap.c

main.c

五、总结


引言

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

一、树

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 的结点称为叶结点; 如上图: B、C、H、I... 等结点为叶结点
  • 分⽀结点/⾮终端结点:度不为 0 的结点; 如上图: D、E、F、G... 等结点为分⽀结点
  • 兄弟结点:具有相同⽗结点的结点互称为兄弟结点(亲兄弟); 如上图: B、C 是兄弟结点
  • 结点的层次:从根开始定义起,根为第 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 HPDestroy(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 HPDestroy(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 HPDestroy(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); } HPDestroy(&hp); } int main() { test01(); return 0; }

五、总结

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

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

Read more

Windows 10/11环境下USB-Blaster驱动安装详解

USB-Blaster驱动在Win10/Win11下的“玄学”安装?一文彻底讲透! 你有没有遇到过这样的场景: FPGA代码写完,板子上电正常,Quartus Prime也打开了——结果点“Program”时弹出红字警告:“ No hardware available ”。 设备管理器里多了一个黄色感叹号的“未知设备”,或者干脆显示“USB-Blaster [Invalid]”。 别急,这几乎每个用Altera(现Intel FPGA)开发的人都踩过的坑。问题不在你的代码,也不在硬件,而是在那个看似简单、实则暗藏玄机的 USB-Blaster 驱动安装 。 尤其是在 Windows 10 和 Windows 11 系统下,微软对驱动签名和内核安全越来越“较真”,传统的“插上去自动识别”早已成为过去式。今天我们就来把这件事从根儿上说清楚:为什么装不上?怎么才能稳稳地装上?以及那些官方文档不会告诉你的实战技巧。 不是所有“USB下载线”

By Ne0inhk
Pi0机器人VLA大模型在昇腾A2平台上的测评

Pi0机器人VLA大模型在昇腾A2平台上的测评

Pi0机器人VLA大模型在昇腾A2平台上的测评文档 * 写在最前面 🌈你好呀!我是 是Yu欸🚀 感谢你的陪伴与支持~ 欢迎添加文末好友🌌 在所有感兴趣的领域扩展知识,不定期掉落福利资讯(*^▽^*) 写在最前面 版权声明:本文为原创,遵循 CC 4.0 BY-SA 协议。转载请注明出处。 随着人工智能技术的持续神户以及人形机器人产业的快速发展,算力在提升机器人运动控制精度、实时响应能力与智能化水平方面的作用日益凸显。为实现降本增效,国产化算力代替需求不断攀升,本文基于国产化适配的 Pi0机器 VLA大模型,在昇腾 Atlas 800I A2服务器上完成部署与测试,结果表明:该模型在推理性能、推理精度及功能完整性等方面,不仅实现了与英伟达同级别硬件相当的算力表现,更在部分场景下表现出更优的运行效率。 这一成果充分表明:经过深度适配的国产大模型与国产算力平台,已具备支撑高端人形机器人智能化发展的核心技术能力。国产算力在人形机器人领域的应用场景广阔,正加速迈向自主可控、高效可靠的全新阶段。 一、测评概述 1.1 测试目的 本测评旨在验证Pi0机器人视觉

By Ne0inhk

openclaw多Agent和多飞书机器人配置

增加Agent多个飞书机器人 一个Agent尽量只用一个飞书机器人配置 一:先增加新的agent # 创建新的Agent,命名为new-agnet openclaw agents add new-agnet # 查看创建结果 openclaw agents list 二:新的agent与新的飞书链接 配置agnet下的channels: 在命令行输入 # 配置new-agnet机器人(替换为实际App ID和App Secret) openclaw config set agents.new-agnet.channels.feishu.appId "你的new-agnet 飞书 App ID" openclaw config set agents.new-agnet.channels.feishu.appSecret "你的new-agnet 飞书 App Secret"

By Ne0inhk
【CANN】Pi0机器人大模型 × 昇腾A2 测评

【CANN】Pi0机器人大模型 × 昇腾A2 测评

【CANN】Pi0机器人大模型 × 昇腾A2 测评 * 写在最前面 🌈你好呀!我是 是Yu欸🚀 感谢你的陪伴与支持~ 欢迎添加文末好友🌌 在所有感兴趣的领域扩展知识,不定期掉落福利资讯(*^▽^*) 写在最前面 版权声明:本文为原创,遵循 CC 4.0 BY-SA 协议。转载请注明出处。 Pi0机器人VLA大模型测评 哈喽大家好呀!我是 是Yu欸。 最近人形机器人和具身智能真的太火了,大家都在聊 Pi0、聊 VLA 大模型。但是,兄弟们,不管是搞科研还是做落地,咱们始终绕不开一个问题——算力。 今天,我们一起把当下最火的 Pi0 机器人视觉-语言-动作大模型,完完整整地部署在国产算力平台上,也就是华为的昇腾 Atlas 800I A2 服务器上。 在跑通仓库模型的基础上,我们做一次性能测评。 我们要测三个最核心的指标:

By Ne0inhk