【嵌入式开发之数据结构】树的基本概念、逻辑结构和四种常用的遍历算法及实现

树(Tree)的定义及基本概念

树的定义

树(Tree)是n(n\geqslant 0)个结点的有限集合T,它满足两个条件:

  • 有且仅有一个特定的称为根(Root)的节点;
  • 其余的节点分为m(m\geqslant 0)个互不相交的有限合集T_{1},T_{2},...,T_{m},其中每一个集合又是一棵树,并称为其根的子树。

表示方法:树形表示法,目录表示法。 

树的基本概念

一个节点的子树的个数称为该节点的度数

一颗树的度数是指该树中节点的最大度数。

度数为零的节点称为树叶或终端节点

度数不为零的节点称为分支节点

一个节点系列k_{1},k_{2},...,k_{i},k_{i+1},...,k_{j},并满足k_{i}k_{i+1}的父节点,就称为一条从k_{1}k_{j}路径,路径的长度为j-1,即路径中的边数

路径中前面的节点是后面节点的祖先,后面节点是前面节点的子孙。 

节点的层数等于父节点的层数加一,根节点的层数定义为一。树中节点层数的最大值称为该树的高度或深度

若树中每个节点的各个子树的排列为从左到右,不能交换,即兄弟之间是有序的,则该树称为有序树

m(m\geqslant 0)棵互不相交的树的集合称为森林

树去掉根节点就成为森林,森林加上一个新的根节点就成为树。

树的逻辑结构

树中任何节点都可以有零个或多个直接后继节点(子节点),但至多只有一个直接前趋节点(父节点),根节点没有前趋节点,叶节点没有后继节点。

二叉树

二叉树的逻辑结构

 二叉树是n(n\geqslant0)个节点的有限集合,或者是空集(n=0) ,或者是由一个根节点以及两棵互不相交的、分别称为左子树右子树的二叉树组成 严格区分左孩子和右孩子,即使只有一个子节点也要区分左右。

二叉树的性质

二叉树第i(i\geqslant 1)层上的节点最多为2^{k-1}-1个。

深度为k(k\geqslant 1)的二叉树最多有2^{k}-1个节点。

满二叉树

深度为k(k\geqslant 1)时,有2^{k}-1个节点的二叉树。

完全二叉树

只有最下面两层有度数小于2的节点,且最下面一层的叶节点集中在最左边的若干位置上。

具有n个节点的完全二叉树的深度为

(log2n)+1log2(n+1)

二叉树的存储结构

二叉树的顺序存储

完全二叉树节点的编号方法是从上到下,从左到右,根节点为1号节点,设完全二叉树的节点数为n,某节点编号为i

  • i> 1(不是根节点)时,有父节点,其编号为\frac{i}{2}
  • 2\times i\leqslant n时,有左孩子,其编号为2\times i,否则没有左孩子,本身是叶节点;
  • 2\times i+1\leqslant n时,有右孩子,其编号为2 \times i+1 ,否则没有右孩子;
  • i为奇数且不为1时,有左兄弟,其编号为i-1,否则没有左兄弟;
  • i为偶数且小于n时,有右兄弟,其编号为i+1,否则没有右兄弟。

n个节点的完全二叉树可以用有n+1个元素的数组进行顺序存储,节点号和数组下标一一对应,下标为零的元素不用

利用以上特性,可以从下标获得节点的逻辑关系。不完全二叉树通过添加虚节点构成完全二叉树,然后用数组存储,这要浪费一些存储空间。

二叉树的链式存储

定义一个二叉树节点类型结构体bitree,每个结点包括三个域:

  • 数据域data:存放每个节点的数据;
  • 左孩子指针域left:存放指向左孩子的指针,如果没有左孩子,则为NULL;
  • 右孩子指针域right:存放指向右孩子的指针,如果没有右孩子,则为NULL。

在头文件tree.h中定义二叉树结构体: 

typedef char data_t;typedef struct node_t {data_t data;//二叉树节点数据域struct node_t *left;//二叉树节点左孩子指针域struct node_t *right;//二叉树节点右孩子指针域
}bitree;//二叉树节点类型别名

二叉树的四种基本遍历算法

遍历的含义

遍历是指沿某条搜索路径周游二叉树,对树中的每一个节点访问一次且仅访问一次。

二叉树是非线性结构,每个结点有两个后继,则存在如何遍历即按什么样的搜索路径进行遍历的问题。

由于二叉树的递归性质,遍历算法也是递归的

对于下面这棵树的遍历,可以通过什么算法实现呢?

遍历算法:先序遍历

先序遍历,是指先访问树根,再访问左子树,最后访问右子树

先序遍历算法:

若二叉树为空树,则空操作,否则:

  • 访问根节点:
  • 先序遍历左子树;
  • 先序遍历右子树。

这是一个递归算法,不断遍历左子树和右子树,直到左子树和右子树均为NULL,结束遍历,依次所访问的节点,即为遍历结果。

为了实现算法,在定义了上述bitree结构体后,声明一个数据类型为bitree的指针函数tree_create()和先序遍历函数preorder();

在tree.h文件中声明树的创建函数和先序遍历函数:
bitree *tree_create();//创建二叉树函数
void preorder(bitree *r);//先序遍历函数
在tree.c中实现树的创建函数:tree_create()
#include <stdio.h>
#include <stdlib.h>
#include "linkqueue.h"//队列头文件,在层次遍历过程中需要用到队列bitree *tree_create() {data_t ch;bitree *r;scanf("%c", &ch);//获取用户输入的字符if (ch == '#') {return NULL;//如果获取到的字符是‘#’则返回NULL}//给二叉树的节点分配内存空间,如果分配失败,则返回NULLif ((r = (bitree *)malloc(sizeof(bitree))) == NULL) {printf("malloc failed.\n");return NULL;}//将获取到的字符存入二叉树节点的data域中,并递归创建左子树和右子树r->data = ch;r->left = tree_create();r->right = tree_create();return r;
}
在tree.c中实现先序遍历函数:preorder()
void preorder(bitree *r) {//传入参数验证,同时也是退出递归的条件if (r == NULL) {return;}//打印访问到的节点存储的值printf("%c", r->data);//递归遍历被访问的节点的左子树和右子树preorder(r->left);preorder(r->right);
}

先序遍历代码看似简单,但其中蕴含一个非常重要的思想,就是递归:

递归是指,原问题的解总是依赖于子问题的解决,在二叉树的先序遍历过程中,要想遍历完整个树,在访问了节点数据data之后,总是依赖于左子树和右子树的是否遍历完成,而左子树和右子树是否遍历完成,同样依赖于它们的左子树和右子树是否遍历完成,以此类推,直到传入preorder()函数的参数为NULL,再返回,完成遍历。

使用递归算法,一定要明确退出递归的条件,否则构成死循环,在这个算法中,退出递归的条件就是r为NULL。

遍历算法:中序遍历

中序遍历,是指先访问左子树,再访问树根,最后访问右子树

若二叉树为空树,则空操作,否则:

  • 中序遍历左子树;
  • 访问根节点;
  • 中序遍历右子树。
在tree.h文件中声明中序遍历函数inorder():
void inorder(bitree *r);//中序遍历函数
在tree.c文件中实现中序遍历函数inorder(): 
void inorder(bitree *r) {//传入参数验证,如果为NULL,返回,也是递归终止条件if (r == NULL) {return;}inorder(r->left);//递归遍历左子树printf("%c", r->data);//访问并打印节点data值inorder(r->right);//递归遍历右子树
}

在这里同样用到递归,退出条件依然是r为NULL,只是是先进行左子树的递归,再打印节点的值,再遍历右子树。 

遍历算法:后序遍历

后序遍历,是指先访问左子树,再访问右子树,最后访问树根

若二叉树为空树,则空操作,否则:

  • 后序遍历左子树;
  • 后序遍历右子树。
  • 访问根节点。
在tree.h文件中声明后序遍历函数postorder():
void postorder(bitree *r);//声明后序遍历函数
 在tree.c文件中实现后序遍历函数postorder(): 
void postorder(bitree *r) {//传入参数验证,如r为NULL,返回,递归终止条件if (r == NULL) {return;}postorder(r->left);//递归遍历左子树postorder(r->right);//递归遍历右子树printf("%c", r->data);//访问节点data值并打印
}

遍历算法:层次遍历

二叉树的层次遍历,是指是从左往右依次访问每层结点的过程。对于顺序表存储的二叉树,层次遍历较容易实现,只需逐层访问存储的结点。对于链表存储的二叉树,可利用队列实现层次遍历。

链表存储的二叉树层次遍历算法

  • 根结点入队,出队并访问;
  • 将其左右孩子入队,出队并访问;
  • 重复此过程直至队列为空。
图1

 在tree.h文件中声明后序遍历函数layerorder():
void layerorder(bitree *r);//层次遍历函数声明
 在linkqueue.h文件中定义节点和队列结构体,并声明相关函数:
#include "tree.h"
typedef bitree * datatype;//给bitree起别名datatype//定义node结构体,并起别名listnode和linklist
typedef struct node {datatype data;struct node *next;
}listnode, *linklist;//定义队列结构体,并起别名linkqueue
typedef struct {linklist front;//队头linklist rear;//队尾
}linkqueue;linkqueue *queue_create();//声明创建队列函数
int enqueue(linkqueue *lq, datatype x);//声明入队函数
datatype dequeue(linkqueue *lq);//声明出队函数
int queue_empty(linkqueue *lq);//声明队列是否为空判断函数
int queue_clear(linkqueue *lq);//声明队列清空函数
linkqueue *queue_free(linkqueue *lq);//声明释放队列函数
 在linkqueue.c文件中实现队列的相关函数:
 实现队列的创建函数queue_create():
#include <stdio.h>
#include <stdlib.h>
//#include "tree.h"
#include "linkqueue.h"linkqueue *queue_create() {linkqueue *lq;if ((lq = (linkqueue *)malloc(sizeof(linkqueue))) == NULL) {printf("malloc linkqueue failed.\n");return NULL;}lq->front = lq->rear = (linklist)malloc(sizeof(listnode));if (lq->front == NULL) {printf("malloc front failed.\n");return NULL;}lq->front->data = 0;lq->front->next = NULL;return lq;
}
 实现入队函数enqueue():
int enqueue(linkqueue *lq, datatype x) {linklist p;if (lq == NULL) {printf("lq is NULL.\n");return -1;}p = (linklist)malloc(sizeof(listnode));if (p == NULL) {printf("malloc front failed.\n");return -1;}p->data = x;p->next = NULL;lq->rear->next = p;lq->rear = p;return 0;
}
  实现出队函数dequeue():
datatype dequeue(linkqueue *lq) {linklist p;if (lq == NULL) {printf("lq is NULL.\n");return NULL;}p = lq->front;lq->front = p->next;free(p);p = NULL;return (lq->front->data);
}
 实现队列判断函数queue_empty():
int queue_empty(linkqueue *lq) {if (lq == NULL) {printf("lq is NULL.\n");return -1;}return (lq->front == lq->rear ? 1: 0);
}
 实现队列清空函数queue_clear():
int queue_clear(linkqueue *lq) {linklist p;if (lq == NULL) {printf("lq is NULL.\n");return -1;}while (lq->front->next) {p = lq->front;lq->front = p->next;//printf("clear free: %d\n", p->data);free(p);p = NULL;}return 0;}
 实现队列释放函数queue_free():
linkqueue *queue_free(linkqueue *lq) {linklist p;if (lq == NULL) {printf("lq is NULL.\n");return NULL;}while (lq->front) {p = lq->front;lq->front = p->next;//printf("free: %d\n", p->data);free(p);}free(lq);lq = NULL;return 0;
}
  在tree.c文件中实现层次遍历函数layerorder(): 
void layerorder(bitree *r) {//声明一个队列lqlinkqueue *lq;//创建队列lq,入创建失败直接返回if ((lq = queue_create()) == NULL)return;//验证传入参数rif (r == NULL) return;//打印访问到的节点数值,并让该节点入队printf("%c", r->data);enqueue(lq, r);//当队列不为空,不断执行左孩子、右孩子的访问,入队和出队while (!queue_empty(lq)) {r = dequeue(lq);if (r->left != NULL) {printf("%c", r->left->data);enqueue(lq, r->left);}if (r->right != NULL) {printf("%c", r->right->data);enqueue(lq, r->right);}}//确认清空队列并释放内存queue_clear(lq);queue_free(lq);
}

遍历算法测试:test.c文件

#include <stdio.h>
#include <stdlib.h>
#include "tree.h"int main(int argc, const char *argv[])
{bitree *r;if((r = tree_create()) == NULL) return -1;//调用先序遍历函数preorder(r);puts("");//调用中序遍历函数inorder(r);puts("");//调用后序遍历函数postorder(r);puts("");//调用层次遍历函数layerorder(r);puts("");return 0;
}

运行结果

将树图1左孩子、有孩子缺失的部分用‘#’填充,运行程序后,输入A#BCEH###FI##J##D#GK###,得到以下结果:

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

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

相关文章

日常开发记录分享——C#控件ToolTip实现分栏显示内容

文章目录 需求来源实现思路实施请看VCR等等别走&#xff0c;有优化 需求来源 需要在鼠标浮动到指定位置后提示出详细的信息&#xff0c;一开始使用的tooltip实现&#xff0c;但是里面的内容效果并不理想&#xff0c;需要有条理性&#xff0c;于是就想到能不能将展示的东西分列…

【Goland编辑器】Goland编辑器设置代码跳转的地方

记录一下&#xff1a; 关闭了插件里面proto相关的插件后&#xff0c; 在这里将Custom folding regions打开后&#xff0c; 点击代码跳转时&#xff0c;由proto生成的文件&#xff0c;就不会再跳转到proto定义处&#xff0c;而是跳转到代码使用的地方【正是我需要的】。

PHP接入consul,注册服务和发现服务【学习笔记】

PHP接入consul,注册服务和发现服务 consul安装 链接: consul安装 启动consul C:\Users\14684>consul agent -dev安装TP5 composer create-project topthink/think5.0.* tp5_pro --prefer-dist配置consul 创建tp5_pro/application/service/Consul.php <?php /*****…

linux版mysql8配置表名不区分大小写

mysql8的安装步骤可参考&#xff1a; mysql8的安装步骤 如果在安装mysql8&#xff0c;初始化之前&#xff0c;没有在my.cnf配置忽略大小写配置&#xff1a; [mysqld] lower_case_table_names1我们就需要重新初始化mysql 1 备份数据库文件 2 停止mysql服务 systemctl stop …

iOS开发设计模式篇第一篇MVC设计模式

目录 1. 引言 2.概念 1.Model 1.职责 2.实现 3.和Controller通信 1.Contrller直接访问Model 2.通过委托(Delegate)模式 3.通知 4.KVO 4.设计的建议 2.View 1.职责 2.实现 3.和Controller通信 1. 目标-动作&#xff08;Target-Action&#xff09;模式 2…

尾气处理系统工作原理

尾气处理系统工作原理 尾气处理系统是汽车发动机排放净化的重要部分&#xff0c;其中主要包括三元催化器、颗粒捕集器和尿素喷射系统等。以下是尾气处理系统的工作原理&#xff1a; 三元催化器&#xff1a;三元催化器是尾气处理系统中最常见的部件&#xff0c;三元催化器是尾气…

汉明权重(Hamming Weight)(统计数据中1的个数)VP-SWAR算法

汉明权重&#xff08;Hamming Weight&#xff09;&#xff08;统计数据中1的个数&#xff09;VP-SWAR算法 定义 汉明重量是一串符号中非零符号的个数。它等于同样长度的全零符号串的汉明距离(在信息论中&#xff0c;两个等长字符串之间的汉明距离等于两个字符串对应位置的不同…

USB转多路串口 - USB CDC设备枚举

先上参考资料&#xff1a; ST社区的&#xff1a; <<USB CDC类入门培训.pdf>>STM32 USB如何配置多个CDC设备状态与枚举过程CDC串口之从认识到认知 USB CDC 类基础 CDC(Communication Device Class)类是 USB2.0 标准下的一个子类&#xff0c;定义了通信相关设备的抽…

在Ubuntu上安装移远EC200M驱动

最近公司在做降本相关工作&#xff0c;考虑移远 EC20 4G模组成本较高&#xff0c;希望通过更低成本替换硬件&#xff0c;最后找到EC200M芯片&#xff0c;虽然EC200M速率(最大下行10M/s 最大上行5M/s)上低于EC20&#xff08;最大下行150M/s 最大上行50M/s&#xff09;,基本上可以…

tongue通lingual:灵根,舌也!

灵&#xff0c;指心灵、精神意识&#xff1b;灵根&#xff1a;汉语“灵根”&#xff0c;通常指人的舌头主。舌头是人心灵的表达根器&#xff0c;因此&#xff0c;灵根——指心灵外化的肉身凭据、可以像树根&#xff08;或一切植物根部&#xff09;一样延伸、像树根一样重要身体…

磁感应传感器 - 从零开始认识各种传感器【第十二期】

1、什么是磁感应传感器 磁感应传感器又叫做磁力计&#xff0c;是可以测量磁场大小或方向的设备。因为地球本质上是一个巨大的磁铁。磁力计可让您测量空间中某一点的磁场强度以及磁场方向。 图1 磁力计 磁力计已广泛应用于各种应用。它们用于测量地球磁场、地理测量、探测潜艇…

Python内存管理:引用计数与垃圾回收

✨ 内容&#xff1a; 在Python中&#xff0c;内存管理是一个重要且常常被忽视的话题。了解Python如何管理内存&#xff0c;不仅能帮助我们编写高效的代码&#xff0c;还能避免潜在的内存泄漏问题。今天&#xff0c;我们将通过一个实际案例&#xff0c;深入探讨Python的内存管理…

RabbitMQ:如何保证消息的可靠性?

RabbitMQ基础 RabbitMQ支持的消息模型 SpringBoot集成RabbitMQ 一、发送者的可靠性 消息从发送者发送消息&#xff0c;到消费者处理消息&#xff0c;需要经过的流程是这样的&#xff1a; 消息从生产者到消费者的每一步都有可能导致消息丢失&#xff1a; 发送消息时丢失&am…

Spring Boot 学习(10)——固基(Idea 配置 git 访问 gitee)

几转眼就过了两个月&#xff0c;其实也没有闲着&#xff0c;学也学了&#xff0c;只是繁杂事多&#xff0c;学的不如以前多&#xff0c;也没有做过笔记了。 以前做开发因条件受限&#xff0c;没有什么 git &#xff0c;也没有 gitee。现在出来混要跟上形势才行&#xff0c;学习…

掌握VR全景技术,需要具备哪些条件?

VR全景技术自从进入市场以来&#xff0c;就在各个行业领域尝试落地运用&#xff0c;包括但不限于广告宣传、学校教育、医疗、工业、农业等领域。随着5G 技术的不断普及&#xff0c;VR全景技术也逐渐被应用到日常生活中的各个方面&#xff0c;从地产中介到车企销售&#xff0c;从…

【数据结构】探索排序的奥秘

若有不懂地方&#xff0c;可查阅我之前文章哦&#xff01; 个人主页&#xff1a;小八哥向前冲~_csdn博客 所属专栏&#xff1a;数据结构_专栏 目录 排序的概念 几种排序方法介绍 冒泡排序 选择排序 插入排序 堆排序 向上调整建堆排序 向下调整建堆排序 希尔排序 快速…

快乐数-快慢指针法

题目描述&#xff1a; 个人题解&#xff1a; 通过反复调用 getNext(n) 得到的链是一个隐式的链表。隐式意味着我们没有实际的链表节点和指针&#xff0c;但数据仍然形成链表结构。起始数字是链表的头 “节点”&#xff0c;链中的所有其他数字都是节点。next 指针是通过调用 ge…

使用两种不同的方法估计几何布朗运动随机过程的参数

使用两种不同的方法估计几何布朗运动随机过程的参数 文章目录 一、说明二. 随机过程三、马尔可夫过程3.1. 维纳进程3.2. 广义维纳过程3.3. 伊藤进程 四、几何布朗运动 &#xff08;GBM&#xff09;五、用于估计GBM工艺参数的MLE方法5.1. 最大似然估计如何工作&#xff1f;5.2、…

2024全国青少年信息素养大赛图形化编程复赛真题大全

2024年全国青少年信息素养大赛图形化编程复赛全国结束了&#xff0c;经过了3次各个赛区&#xff08;7月6日、13日、20日&#xff09;图形化编程小低组、小高组整体来看&#xff0c;真题出的都比较有水平&#xff0c;2024年全国青少年信息素养大赛总决赛将在2024年8月16日~20日在…

MySQL面试篇章——MySQL索引

文章目录 MySQL 索引索引分类索引创建和删除索引的执行过程explain 查看执行计划explain 结果字段分析 索引的底层实现原理B-树B树哈希索引 聚集和非聚集索引MyISAM&#xff08;\*.MYD&#xff0c;*.MYI&#xff09;主键索引辅助索引&#xff08;二级索引&#xff09; InnoDB&a…