【数据结构】二叉树的创建和遍历:前序遍历,中序遍历,后序遍历,层次遍历

目录

一、二叉树的定义

1、二叉树的定义

2、二叉树的五种形态 

 二叉树的子树 :

3、满二叉树与完全二叉树 

4、二叉树的性质 

5、二叉树的存储结构

1、顺序存储

​编辑

2、链式存储

 二、二叉树的遍历

按照前序序列构建二叉树

1、前 (先) 序遍历(Preorder Traversal )

前序遍历动态过程图:  

 下面是前序遍历的递归图解:

前序遍历代码及注释 :

2. 中序遍历(Inorder Traversal) 

中序遍历动态过程图: 

中序遍历代码及注释: 

3. 后序遍历(Postorder Traversal)

后序遍历动态过程图:  

后序遍历代码及注释: 

4、层序遍历 

层序遍历代码及注释: 


一、二叉树的定义

1、二叉树的定义

二叉树(Binary Tree)是有n(n≥0)个结点的有限集合:
(1)  该集合或者为空(n=0);
(2)或者由一个根结点及两个不相交的分别称为左子树和右子树组成的非空树;
(3)左子树和右子树同样又都是二叉树。
在一棵非空的二叉树中,每个结点至多只有两棵子树,分别称为左子树和右子树,且左右子树的次序不能任意交换。所以,二叉树是特殊的有序树。值得注意的是,由于二叉树上任结点的子树有左、右之分,因此即使一个结点只有一棵非空子树,仍须区别它是该结点的左子树还是右子树,这是与树不同的。


2、二叉树的五种形态 

 二叉树的子树 :

        在二叉树中,一个子树是指由二叉树中的某个节点及其后代节点组成的树。换句话说,对于一个给定的二叉树,可以选择其中的一个节点作为子树的根节点,并且包含该节点的所有后代节点,形成一个新的子树。

具体定义如下:
        在一个二叉树中,每个节点最多只有两个子节点,分别为左子节点和右子节点。对于任意一个节点,在它的左子节点和右子节点上又可以分别构成两个独立的子树,这样就形成了一个递归的结构。


3、满二叉树与完全二叉树 

1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。                                     
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。特点:树高为h时,前h-1层节点为满。 要注意的是满二叉树是一种特殊的完全二叉树


4、二叉树的性质 

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点。
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h-1。
  3. 对任何一棵二叉树, 如果度为0的叶结点个数为n0,度为2的分支结点个数为n2,则有 n0 = n2 + 1。
  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度h= log2(n+1)。(这里的log是以2为底的对数)
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
    a. 若i > 0,i位置节点的双亲序号为(i-1)/2,若i = 0,则i为根节点编号,无双亲节点。
    b. 若2i+1 < n,左孩子序号为2i+1,若2i+1 >= n,则无左孩子。
    c. 若2i+2 < n,右孩子序号为2i+2,若2i+2 >= n,则无右孩子。

5、二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

1、顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的博客会专门讲解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。


2、链式存储

 二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,本节内容我们主要讲解二叉链式存储结构。

链式二叉树结点定义如下:

typedef char BTDataType; // 使用typedef关键字给char类型取了一个别名BTDataTypetypedef struct BTNode // 使用struct关键字定义了一个名为BTNode的结构体
{BTDataType data;        // 二叉树节点的数据域,用于存储具体的数据值struct BTNode* left;    // 二叉树节点的左子节点指针,指向左子节点struct BTNode* right;   // 二叉树节点的右子节点指针,指向右子节点
} BTNode;   // 使用BTNode作为该结构体类型的别名


 二、二叉树的遍历

按照前序序列构建二叉树

在对二叉树进行遍历之前我们先对通过前序遍历的数组"ABD##E#H##CF##G##"构建一颗二叉树。注意:'#' 字符代表该节点为空。

// 前序建立二叉树的函数
BTNode* CreateBinaryTreePre(BTDataType* a, int n, int* pi)
{// 如果数组索引超出数组长度,返回空指针if (*pi >= n){return NULL;}// 如果当前位置的值为 '#',表示当前位置为空节点,将数组索引向后移动,并返回空指针if (a[*pi] == '#'){(*pi)++;return NULL;}// 动态分配一个新的二叉树节点BTNode* root = (BTNode*)malloc(sizeof(BTNode));// 如果内存分配成功if (root){// 将当前位置的值存储到新节点的数据域中root->data = a[(*pi)++];// 递归调用CreateBinaryTreePre函数,构建新节点的左子树root->left = CreateBinaryTreePre(a, n, pi);// 递归调用CreateBinaryTreePre函数,构建新节点的右子树root->right = CreateBinaryTreePre(a, n, pi);}else{// 如果内存分配失败,打印错误信息并退出程序perror("malloc fail!");exit(-1);}return root;  // 返回根节点
}// 主函数
int main()
{BTNode* root;   // 定义二叉树的根节点指针BTDataType a[] = { 'A','B','D','#','#','E','#','H','#','#','C','F','#','#','G','#','#' ,'\0' };int n = strlen(a);int pi = 0;   // 定义一个变量用于追踪数组的索引位置// 调用CreateBinaryTreePre函数,传入数组a、数组长度n和索引位置的指针pi,//生成二叉树,并返回根节点指针root = CreateBinaryTreePre(a, n, &pi);return 0;
}

1、前 (先) 序遍历(Preorder Traversal )

 访问根结点的操作发生在遍历其左右子树之前——即: 根节点 -> 左子树 -> 右子树。

     前序遍历二叉树的算法可以按照如下的步骤实现:

  1. 如果二叉树为空,直接返回。
  2. 访问当前节点,即输出当前节点的值。
  3. 对当前节点的左子树进行前序遍历。
  4. 对当前节点的右子树进行前序遍历。

我们如何理解遍历顺序呢?博主在这里分享一下自己的方法:

        对于一棵二叉树,我们先找到其根节点,打印出根结点的值后,我们对其左子树进行遍历。由于一颗二叉树可以划分出许多子树,那么遍历左子树时我们即可将当前节点(即根结点的左孩子结点)看作左子树的根结点,然后对当前子树再进行根节点 -> 左子树 -> 右子树 的遍历方法,如果当前结点为空,返回上一层递归。在当前根节点的左子树遍历完成时我们再对右子树进行遍历,如此循环往复,直至遍历完整棵树停止。

        简而言之,就是当遍历到一个新结点时,把当前结点当作根节点,接着去遍历当前结点的左右子树。进入下一个子树遍历时,继续把当前结点当作根节点,如此循环往复下去,直至遇到空结点,递归开始回溯。


前序遍历动态过程图:  


 下面是前序遍历的递归图解:


前序遍历代码及注释 :
// 前序遍历二叉树
void PrintPreOrder(BTNode* root)
{// 如果当前节点为空,返回if (!root){return;}// 打印当前节点的值printf("%c ", root->data);// 递归遍历左子树PrintPreOrder(root->left);// 递归遍历右子树PrintPreOrder(root->right);
}

2. 中序遍历(Inorder Traversal) 

访问根结点的操作发生在遍历其左右子树之中(间)——即: 左子树 --> 根节点 --> 右子树。

中序遍历二叉树的算法可以按照如下步骤实现:

  1. 如果二叉树为空,直接返回。
  2. 对当前节点的左子树进行中序遍历。
  3. 访问当前节点,即输出当前节点的值。
  4. 对当前节点的右子树进行中序遍历。

 对于中序遍历,我们采取类似的方法。当我们遍历时,将当前结点当作子树的根节点,先去寻找该根节点有没有左孩子,如果有,我们就向左进行遍历,当到达新结点时,我们依旧将其看作根节点去寻找该结点的左孩子,如果当前结点为空,再回溯到其父结点打印数据,再去寻找它的右孩子。当当前子树遍历完成时,我们回溯到此子树根节点的上一层的节点,打印节点数据之后再去找该结点的右孩子。如此循环往复,直至遍历完整棵树停止。


中序遍历动态过程图: 


中序遍历代码及注释: 
// 中序遍历
void PrintInOrder(BTNode* root)
{// 如果当前节点为空,即已经到达叶子节点或者是空树的情况,直接返回if (!root){return;}// 递归调用中序遍历函数,遍历左子树PrintInOrder(root->left);// 打印当前节点的数据printf("%c ", root->data);// 递归调用中序遍历函数,遍历右子树PrintInOrder(root->right);
}

3. 后序遍历(Postorder Traversal)

访问根结点的操作发生在遍历其左右子树之后——即: 左子树 -->右子树 --> 根节点 。

后序遍历二叉树的算法可以按照如下的步骤实现:

  1. 如果二叉树为空,直接返回。
  2. 对当前节点的左子树进行后序遍历。
  3. 对当前节点的右子树进行后序遍历。
  4. 访问当前节点,即输出当前节点的值。

 对于后序遍历,我们依然按照之前的方法。当我们遍历时,将当前结点当作子树的根节点,先去寻找该根节点有没有左孩子,如果有,我们就向左进行遍历,直至树的底部;如果当前结点为空,返回上一层递归,再去判断当前节点有没有右孩子,如果有我们再向右遍历,重复上面的过程。当当前子树左右孩子遍历完后,我们回退至子树的根结点打印数据。如此循环往复,直至遍历完整棵树停止。


后序遍历动态过程图:  


后序遍历代码及注释: 
// 后序遍历
void PrintPostOrder(BTNode* root)
{// 如果当前节点为空,直接返回if (!root){return;}// 递归调用后序遍历函数,遍历左子树PrintPostOrder(root->left);// 递归调用后序遍历函数,遍历右子树PrintPostOrder(root->right);// 打印当前节点的数据printf("%c ", root->data);
}

4、层序遍历 

层序遍历是二叉树中最常用的遍历方法之一,它依次按层遍历二叉树中的结点。

具体实现思路如下:

  1. 创建一个队列,将根节点插入队列中。
  2. 取出队列的首个节点,访问该节点。
  3. 若该节点有左子节点,将左子节点插入队列中。
  4. 若该节点有右子节点,将右子节点插入队列中。
  5. 重复步骤 2 ~ 4,直到队列为空为止。


层序遍历代码及注释: 
typedef char BTDataType;  // 二叉树中每个节点所存储的数据类型为 chartypedef struct BTNode
{BTDataType data;  // 节点存储的数据struct BTNode* left;  // 左子节点struct BTNode* right;  // 右子节点
}BTNode;  // 定义二叉树结构体typedef BTNode* QueueDataType;  // 队列中每个节点所存储的数据类型为 BTNode 指针typedef struct QNode
{QueueDataType val;  // 节点存储的数据struct QNode* next;  // 指向下一个节点的指针
}QNode;  // 定义队列节点结构体typedef struct Queue
{QNode* front;  // 队头指针QNode* rear;  // 队尾指针int size;  // 队列中元素的个数
}Queue;  // 定义队列结构体// 初始化队列
void QueueInit(Queue* q)
{assert(q);q->front = NULL;q->rear = NULL;q->size = 0;
}// 入队
void QueuePush(Queue* q, QueueDataType x)
{assert(q);QNode* temp = (QNode*)malloc(sizeof(QNode));if (temp == NULL){perror("malloc fail!");exit(-1);}temp->val = x;temp->next = NULL;if (q->front == NULL){q->front = q->rear = temp;  // 队列为空时,新元素既是队头也是队尾}else{q->rear->next = temp;  // 把新元素连接到队尾后q->rear = temp;  // 更新队尾指针为新的元素}q->size++;  // 队列元素个数加1
}// 判断队列是否为空
bool QueueEmpty(Queue* q)
{assert(q);if (q->front == NULL){return true;  // 队列为空}return false;  // 队列不为空
}// 获取队头元素
QueueDataType QueueTop(Queue* q)
{assert(q);if (!QueueEmpty(q)){return q->front->val;  // 返回队头元素的值}else{printf("队空,无法获取队头元素!\n");exit(-1);}
}// 出队
void QueuePop(Queue* q)
{assert(q);if (!QueueEmpty(q)){QNode* temp = q->front;  // 保存队头指针q->front = q->front->next;  // 移动队头指针到下一个元素free(temp);  // 释放原队头节点的内存空间q->size--;  // 队列元素个数减1}else{printf("队空,无法删除队头元素!\n");exit(-1);}
}// 获取队列元素个数
int QueueSize(Queue* q)
{assert(q);return q->size;  // 返回队列元素个数
}// 二叉树的层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{assert(root);Queue q;QueueInit(&q);  // 初始化队列QueuePush(&q, root);  // 将根节点入队int TSize = 1;while (!QueueEmpty(&q)){// 遍历当前层级的结点while (TSize > 0){BTNode* cur = QueueTop(&q);  // 获取队头元素QueuePop(&q);  // 出队printf("%c ", cur->data);  // 输出当前结点的数据if (cur->left){QueuePush(&q, cur->left);  // 左子节点入队}if (cur->right){QueuePush(&q, cur->right);  // 右子节点入队}TSize--;  // 减少当前层级元素个数}printf("\n");  // 输出换行符表示当前层级遍历结束TSize = QueueSize(&q);  // 更新当前层级元素个数}
}

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

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

相关文章

数据结构入门到入土——链表(2)

目录 一&#xff0c;与链表相关的题目&#xff08;2&#xff09; 1.输入两个链表&#xff0c;找出它们的第一个公共节点 2.给定一个链表&#xff0c;判断链表中是否有环 3.给定一个链表&#xff0c;返回链表开始入环的第一个节点&#xff0c;若无则返回null 一&#xff0c;…

09、docker 安装nacos并配置mysql存储配置信息

docker 安装nacos并配置mysql存储配置信息 1、docker启动nacos的各种方式2、Docker安装nacos3、MySQL中新建nacos的数据库4、挂载数据or配置目录5、运行 1、docker启动nacos的各种方式 内嵌derby数据源 docker run -d \ -e PREFER_HOST_MODEhostname \ -e SPRING_DATASOURCE_…

计算机毕业论文内容参考|基于智能搜索引擎的图书管理系统的设计与实现

文章目录 摘要前言绪论课题背景国内外现状与趋势课题内容相关技术与方法介绍系统分析系统设计系统实现系统测试总结与展望摘要 本文介绍了基于智能搜索引擎的图书管理系统的设计与实现。该系统旨在提供一个高效、智能化的图书管理平台,帮助用户更快、更准确地找到所需的图书资…

XCTF-Misc1 USB键盘流量分析

m0_01 附件是一个USB流量文件 分析 1.键盘流量 USB协议数据部分在Leftover Capture Data域中&#xff0c;数据长度为八个字节&#xff0c;其中键盘击健信息集中在第三个字节中。 usb keyboard映射表&#xff1a;USB协议中HID设备描述符以及键盘按键值对应编码表 2.USB…

Java:Stream流

文章目录 1、体验Stream流2、Stream流的常见生成方式3、Stream流中间操作方法4、Stream流终结操作方法5、Stream流的收集操作6、Stream流综合练习6.1 练习16.2 练习26.3 练习3 以下代码使用JDK11编写。 1、体验Stream流 &#xff08;1&#xff09;案例需求 按照下面的要求完成…

SolidUI Gitee GVP

感谢Gitee&#xff0c;我是一个典型“吃软不吃硬”的人。奖励可以促使我进步&#xff0c;而批评往往不会得到我的重视。 我对开源有自己独特的视角&#xff0c;我只参与那些在我看来高于自身认知水平的项目。 这么多年来&#xff0c;我就像走台阶一样&#xff0c;一步一步参与…

Elasticsearch:带有自查询检索器的聊天机器人示例

本工作簿演示了 Elasticsearch 的自查询检索器 (self-query retriever) 将问题转换为结构化查询并将结构化查询应用于 Elasticsearch 索引的示例。 在开始之前&#xff0c;我们首先使用 langchain 将文档分割成块&#xff0c;然后使用 ElasticsearchStore.from_documents 创建…

【微服务】springcloud集成skywalking实现全链路追踪

目录 一、前言 二、环境准备 2.1 软件环境 2.2 微服务模块 2.3 环境搭建 2.3.1 下载安装包 2.3.2 解压并启动服务 2.3.3 访问web界面 三、搭建springcloud微服务 3.1 顶层公共依赖 3.2 用户服务模块 3.2.1 准备测试使用数据库 3.2.2 添加依赖 3.2.3 添加配置文件 …

如何保证本地缓存的一致性(和分布式缓存)

保证本地缓存和分布式缓存的一致性是一个关键的问题&#xff0c;因为这可以确保系统的健壮性和响应速度。以下是一些在Java中实现这一目标的方法&#xff1a; 1.使用一致性哈希&#xff1a;一致性哈希是一种特殊的哈希技术&#xff0c;它能够在节点增减时最小化哈希环上的数据分…

c++基础(对c的扩展)

文章目录 命令空间引用基本本质引用作为参数引用的使用场景 内联函数引出基本概念 函数补充默认参数函数重载c中函数重载定义条件函数重载的原理 命令空间 定义 namespace是单独的作用域 两者不会相互干涉 namespace 名字 { //变量 函数 等等 }eg namespace nameA {int num;v…

啊哈c语言——逻辑挑战9:水仙花数

有一种三位数特别奇怪&#xff0c;这种数的“个位数的立方”加上“十位数的 立方”再加上“百位数的立方”恰好等于这个数。例如&#xff1a; 153111555333&#xff0c;我们为这种特殊的三位数起了一个很好听的名字——“水仙花数”&#xff0c;那么请你找出所有的“水仙花数”…

Vue2 - Vue.observable 介绍

目录 1&#xff0c;介绍2&#xff0c;使用场景和 Vue 实例的区别 1&#xff0c;介绍 官网参考 可以让一个对象变成响应式数据。在 Vue 内部就是用它来处理传递给 Vue 的 data 对象&#xff0c;或是在单文件组件中 data() 返回的对象。 var vm new Vue({data: {count: 0} })…

MySQL学习笔记2: MySQL的前置知识

目录 1. MySQL是什么?2. 什么是客户端&#xff0c;什么是服务器&#xff1f;3. 服务器的特点4. 安装mysql5. mysql 客户端6. mysql 服务器7. mysql的本体8. MySQL 使用什么来存储数据&#xff1f;9. 数据库的多种含义10. MySQL 存储数据的组织方式 1. MySQL是什么? MySQL 是…

【Unity】 HTFramework框架(四十七)编辑器日志中使用超链接的技巧

更新日期&#xff1a;2024年1月3日。 Github源码&#xff1a;[点我获取源码] Gitee源码&#xff1a;[点我获取源码] 索引 日志中使用超链接超链接-网络地址超链接-本地地址超链接-项目资源文件超链接-脚本对象 日志中使用超链接 在编辑器控制台Console中的日志是支持富文本的&…

K8S集群部署解决工作节点couldn‘t get current server API group list问题

最近在自己电脑上装了VMWare Player&#xff0c;在上面装了两个Ubuntu虚拟机&#xff0c;为了方便学习云原生技术&#xff0c;决定在上面装一个2个节点&#xff08;一个控制面&#xff0c;一个工作节点&#xff09;的K8S集群。 参考这篇文章&#xff1a; Ubuntu 22.04 搭建K8…

Linux驱动学习—中断

1、中断基础概念 1.1 什么是中断 CPU在正常运行期间&#xff0c;由外部或者内部引起的时间&#xff0c;让CPU停下当前正在运行的程序&#xff0c;转而去执行触发他的中断所对应的程序&#xff0c;这就是中断。 响应中断的过程&#xff1a; <1>中断请求 <2>中断…

探索网络连接的netstat

文章目录 探索网络连接的netstat基本概述更多信息 探索网络连接的netstat 在Linux系统中&#xff0c;网络是至关重要的部分&#xff0c;而netstat命令是管理和监视网络连接的强大工具之一。 它提供了关于网络接口和路由表的详细信息&#xff0c;有助于了解网络连接状态、统计…

全国计算机等级考试| 二级Python | 真题及解析(10)

一、选择题 1.要实现将实数型变量a的值保留三位小数,以下python可以实现的是( ) A.a%0.001 B.a//0.001 C.round(a,3) D.round(3,a) 2.在Python中要交换变量a和b中的值,应使用的语句组是( )。 A…

通信原理期末复习——基础小题汇总(二)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;V…

【Docker】容器的相关命令

上一篇&#xff1a;创建&#xff0c;查看&#xff0c;进入容器 https://blog.csdn.net/m0_67930426/article/details/135430093?spm1001.2014.3001.5502 目录 1. 关闭容器 2.启动容器 3.删除容器 4.查看容器的信息 查看容器 1. 关闭容器 从图上来看&#xff0c;容器 aa…