当前位置: 首页 > news >正文

【数据结构】二叉树<遍历>

【二叉树遍历】|-前序-中序-后序-层序-|

  • <二叉树的遍历>
    • 1.前序遍历【递归】
    • 2.中序遍历【递归】
    • 3.后序遍历【递归】
    • 4.层序遍历【非递归】
      • 4.1判断是否是完全二叉树

<二叉树的遍历>

在学习二叉树遍历之前我们先了解下二叉树的概念。
二叉树是:
1.空树
2.非空:根节点,根节点的左子树,根节点的右子树构造。

在这里插入图片描述
学习二叉树结构,最简单的方式就是遍历了。
二叉树的遍历就是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个节点只操作一次。
访问结点所做的操作依赖于具体的应用问题。

二叉树的遍历分为
1.<前序遍历>[Preorder Traversal]
访问根节点的操作发生在遍历左右子树之前。
也就是对于一个节点,它要求先访问这个节点的内容,然后再去遍历左子树,当左子树遍历完后,再遍历右子树。

2.<中序遍历>[Inorder Traversal]
访问根节点的操作发生在遍历其左右子树之中
也就是对于一个节点,它要求先遍历左子树,当左子树都遍历完,再回来访问节点里的内容,然后再遍历右子树。

3.<后续遍历>[Postorder Traversal]
访问根节点的操作发生在遍历其左右子树之后
也就是对于一个节点,它要求先遍历完左子树,再遍历完右子树,最后回来的时候再访问节点的内容。
4.<层序遍历>
层序遍历,顾名思义,一层一层的遍历即可。
从第一层开始遍历,遍历完第一层再遍历下一层。

我们为了好验证二叉树的遍历操作,手动创造一个二叉树,也就是下图在这里插入图片描述
这样,用代码来实现就是这样:

#include <stdio.h>
#include <stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(BTDataType x)
{BTNode* ret =(BTNode*)malloc(sizeof(BTNode));ret->data = x;ret->left = NULL;ret->right = NULL;return ret;
}
BTNode* CreatBinaryTree()
{BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;return node1;
}

1.前序遍历【递归】

我们为了真正展现前序遍历在二叉树中是如何实现的,将空节点也打印出来。这样就可以清晰的看出来遍历的过程。

// 二叉树前序遍历-<根节点-左子树-右子树>-
void PreOrder(BTNode* root)
{if (root == NULL)//如果遇到空节点就返回{printf("NULL ");return;}printf("%d ", root->data);//先访问根节点内容,打印完节点内容后再进入左子树。PreOrder(root->left);//进入左子树PreOrder(root->right);//进入右子树
}

在这里插入图片描述

在这里插入图片描述

根据结果你能想明白怎么遍历的吗?

递归展开图:

在这里插入图片描述

在这里插入图片描述

2.中序遍历【递归】

// 二叉树中序遍历
void InOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}InOrder(root->left);//先遍历左子树printf("%d ", root->data);//遍历完左子树后再访问节点内容InOrder(root->right);//访问完节点内容后再遍历右子树
}

在这里插入图片描述
在这里插入图片描述
递归展开图
在这里插入图片描述

3.后序遍历【递归】

void PostOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
而后序遍历这种特点很适合用在二叉树的销毁上去。

因为相比较前序遍历,如果先销毁了节点,那它的左右子树就无法找到了。
但后续遍历不一样,后序遍历是先遍历左右子树,最后再访问节点。
所以我们只要使用后序遍历,先销毁左右子树,再销毁节点就可以了。
比如:二叉树的销毁

void BTreeDestroy(BTNode* root)
{if (root == NULL){return;}BTreeDestroy(root->left);//先销毁左子树BTreeDestroy(root->right);//再销毁右子树free(root);//最后再销毁节点root = NULL;
}

4.层序遍历【非递归】

上面三个都是属于递归形式的遍历,层序遍历是非递归的。

怎么进行层序遍历呢?
这个就需要用到队列来解决了。
思想:
出上一层,带入下一层。

一开始让根节点入队列,那队列中就有元素存在,不是空队列了。
然后接下来就是不断的出队列中的根节点,每一次出队列中的根节点时,都要将
该根节点的孩子插入到队列的后面去。 也就是出根节点,带入它们的孩子进来。直到队列中没有数据为止。

在这里插入图片描述
不过注意的是,队列中不是真正节点,而是指向节点的指针,如果将节点插入进去,那怎么找到它们的孩子呢?
所以我们插入进入的是指向节点的指针。

typedef struct BTreeNode* QData;//注意这里将队列中元素的类型改成指向节点的指针
typedef struct QNode
{struct QNode* next;QData data;//队列的元素是指向节点的指针
}QNode;
//因为队列的数据结构操作需要找尾,这就需要传多个参数了,很麻烦,所以我们再分装个结构体将多个数据变成一个
typedef struct Queue
{QNode* head;QNode* tail;int size;
}Queue;
void QueueInit(Queue *pq);//初始化队列
void QueueDestroy(Queue *pq);//销毁队列
void QueuePush(Queue*pq ,QData x);//入队,从队尾插入一个数据,尾删
void QueuePop(Queue *pq);//出队,从队头删除数据,头删
bool QueueEmpty(Queue *pq);//判断队列是否为空
int QueueSize(Queue*pq);//获得队列有效数据个数大小
QData QueueFront(Queue*pq);//获取队头数据
QData QueueBack(Queue*pq);//获取队尾数据
#include "queue.h"
void QueueInit(Queue* pq)//初始化队列
{assert(pq);pq->head = pq->tail = NULL;pq->size = 0;
}
void QueueDestroy(Queue* pq)//销毁队列
{QNode* cur = pq->head;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->head = pq->tail = NULL;pq->size = 0;
}
void QueuePush(Queue* pq, QData x)//入队,从队尾插入一个数据,尾删
{assert(pq);
/*	QNode* cur = pq->head;*/QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc");}newnode->data=x;newnode->next = NULL;if (pq->head == NULL){//赋值pq->head = pq->tail = newnode;}else{pq->tail->next = newnode;//更新tail的位置pq->tail = newnode;}pq->size++;}
void QueuePop(Queue* pq)//出队,从队头删除数据,头删
{assert(pq);//头删之前需要判断链队列是否为空assert(pq->head!=NULL);QNode* next = pq->head->next;free(pq->head);pq->head = next;if (pq->head==NULL)//只管头删,最后再处理。{pq->tail=NULL;}pq->size--;
}bool QueueEmpty(Queue* pq)//判断队列是否为空----主要size的作用
{assert(pq);return pq->size == 0;//return pq->head=pq->tailk=NULL;
}
int QueueSize(Queue* pq)//获得队列有效数据个数大小
{assert(pq);return pq->size;
}QData QueueFront(Queue* pq)//获取队头数据
{assert(pq);assert(!QueueEmpty(pq));return pq->head->data;
}QData QueueBack(Queue* pq)//获取队尾数据
{assert(pq);assert(!QueueEmpty(pq));return pq->tail->data;
}

以上是创建一个队列,接下来就是进行二叉树的层序遍历了。

void LevOlder(BTNode* root)//层序遍历--
{Queue q;//定义一个队列QueueInit(&q);//初始化队列//首先将根 指针插入到队列里去if (root){QueuePush(&q, root);}//再出上一层带入下一层while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);//保存一下这个要出队列的指向结点的指针QueuePop(&q);printf("%d ", front->data);//出完后再将它的孩子指针带入进来if (front->left){QueuePush(&q, front->left);}if (front->right){QueuePush(&q, front->right);}}printf("\n");QueueDestroy(&q);
}

在这里插入图片描述

4.1判断是否是完全二叉树

如何判断一个二叉树树是否是完全二叉树呢?
首先我们需要了解什么是完全二叉树

【完全二叉树】
1.前n-1层都是满的二叉树。
2.最后一层从左到右是连续的。

特点:
1.非空节点是连续的。
2.空节点也是连续的。
3.至多有一个度为1的节点。

我们根据完全二叉树非空节点都是连续的这一特性,来作下面的思路:

如果对完全二叉树进行层序遍历,那么第一次出现空节点的地方就是最后一个节点的后面。 而后面就不能再出现非空节点了,后面应该都是空。
所以我们可以做出这样判断:层序遍历二叉树,如果第一次出现空之后,再出现非空节点的一定不是完全二叉树,如果后面只有空则是完全二叉树。

不过要注意的是,这里的层序遍历与原来的层序遍历不一样,原来的层序遍历,只会将根节点插入到队列中去,不会将空节点插入到队中去,而现在需要将空节点也插入到队列中去,如果出队列中的元素,出出队列的是一个空节点,那么我们就可以进行判断,是否后面还会出现非空节点呢?

//判断是否是完全二叉树,利用完全二叉树性质--非空结点是连续的,一旦出现空,后面就不应该再出现空结点。所以利用层序遍历,当第一次出现
//空时,就可以进行判断后面是否会出现非空结点。这里不同与普通层序遍历,NULL也进队列,而且出队列的结点有两种可能一种为空,一种不为空,不像层序遍历只出非空结点
{
bool BTreeCompele(BTNode* root)
{Queue q;QueueInit(&q);if (root)//将根节点插入到队列中{QueuePush(&q, root);}while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);//出队列中的元素if (front == NULL){break;}//如果front不为空,就将它的孩子插入到队列中去,空节点也插入进去,不需要讨论QueuePush(&q, front->left);QueuePush(&q, front->right);}//break 跳出来需要判断是否后面还会出现非空节点while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);//出队列中的元素if (front)//如果队列中出的节点不为空节点{QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}

相关文章:

【数据结构】二叉树<遍历>

【二叉树遍历】|-前序-中序-后序-层序-|<二叉树的遍历>1.前序遍历【递归】2.中序遍历【递归】3.后序遍历【递归】4.层序遍历【非递归】4.1判断是否是完全二叉树<二叉树的遍历> 在学习二叉树遍历之前我们先了解下二叉树的概念。 二叉树是&#xff1a; 1.空树 2.非空…...

linux查看硬件信息

dmidecode用于在linux下获取硬件信息&#xff0c;遵循SMBIOS/DMI标准&#xff0c;可获取包括BIOS、系统、主板、处理器、内存、缓存等等硬件信息 1、查看CPU信息cat /proc/cpuinfo、lscpu 型号&#xff1a;cat /proc/cpuinfo|grep name|cut -f2 -d:|uniq -c 物理核&#xff1a…...

吐血整理,互联网大厂最常见的 1120 道 Java 面试题(带答案)整理

前言 作为一个 Java 程序员&#xff0c;你平时总是陷在业务开发里&#xff0c;每天噼里啪啦忙敲着代码&#xff0c;上到系统开发&#xff0c;下到 Bug 修改&#xff0c;你感觉自己无所不能。然而偶尔的一次聚会&#xff0c;你听说和自己一起出道的同学早已经年薪 50 万&#x…...

RabbitMQ如何避免消息丢失

目录1.生产者没有成功把消息发送到MQ2.RabbitMQ接收到消息之后丢失了消息3.消费者弄丢了消息前言 首先明确一点一条消息的传送流程&#xff1a;生产者->MQ->消费者 我们根据这三个依次讨论 1.生产者没有成功把消息发送到MQ 丢失的原因&#xff1a;因为网络传输的不稳定…...

做算法题的正确姿势(不断更新)

不停的反思自己&#xff0c;总结建议 做一道算法题&#xff0c;不能去死磕。 如果看一道题&#xff0c;半小时内&#xff0c;没有清晰的思路&#xff0c;就看题解&#xff01;&#xff01;&#xff01;你可能觉得你有点思路&#xff0c;就往里死钻&#xff0c;结果可能就像进…...

p85 CTF夺旗-JAVA考点反编译XXE反序列化

数据来源 图片来源 Java 常考点及出题思路 考点技术&#xff1a;xxe&#xff0c;spel 表达式&#xff0c;反序列化&#xff0c;文件安全&#xff0c;最新框架插件漏洞等 设法间接给出源码或相关配置提示文件&#xff0c;间接性源码或直接源码体现等形式 https://www.cnblog…...

FastJson——JSO字符串与对象的相互转化

一、FastJson介绍 ​ Fastjson是阿里巴巴的开源SON解析库它可以解析JSON格式的字符串&#xff0c;支持将java Bean序列化为ISON字符串&#xff0c;也可以从JSON字符串反序列化到JavaBean。 Fastjson的优点 速度快 fastjson相对其他JSON库的特点是快&#xff0c;从2011年fastj…...

《程序员面试金典(第6版)》面试题 08.08. 有重复字符串的排列组合(回溯算法,全排列问题)C++

题目描述 有重复字符串的排列组合。编写一种方法&#xff0c;计算某字符串的所有排列组合。 示例1: 输入&#xff1a;S “qqe” 输出&#xff1a;[“eqq”,“qeq”,“qqe”] 示例2: 输入&#xff1a;S “ab” 输出&#xff1a;[“ab”, “ba”] 提示: 字符都是英文字母。…...

k8s API限流——server级别整体限流和客户端限流

1. 背景 为了防止突发流量影响apiserver可用性&#xff0c;k8s支持多种限流配置&#xff0c;包括&#xff1a; MaxInFlightLimit&#xff0c;server级别整体限流Client限流EventRateLimit, 限制eventAPF&#xff0c;更细力度的限制配置 1.1 MaxInFlightLimit限流 apiserver…...

在华为做了三年软件测试被裁了,我该怎么办

近年来&#xff0c;随着经济环境的变化和企业战略的调整&#xff0c;员工被裁员的情况变得越来越普遍。无论是因为企业经营困难还是因为业务调整&#xff0c;员工们都可能面临被裁员的风险。如果你也遇到了这样的情况&#xff0c;那么你应该怎么办呢&#xff1f; 首先&#xf…...

Spring cloud 限流的多种方式

在频繁的网络请求时&#xff0c;服务有时候也会受到很大的压力&#xff0c;尤其是那种网络攻击&#xff0c;非法的。这样的情形有时候需要作一些限制。本文主要介绍了两种限流方法&#xff0c;感兴趣的可以了解一下 目录 一、实战基于 Spring cloud Gateway 的限流 二、基于阿…...

Linux命令·top

top命令是Linux下常用的性能分析工具&#xff0c;能够实时显示系统中各个进程的资源占用状况&#xff0c;类似于Windows的任务管理器。下面详细介绍它的使用方法。top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止…...

springmvc之系列文章

springmvc之编程步骤 springmvc初始化过程 用WebServlet和WebFilter干掉web.xml 没有web.xml怎么写web程序 一次GET请求在springmvc中是的处理流程 springMVC的handler都有哪些类型 springmvc主要组件简单介绍 springmvc 的Servlet WebApplicationContext springmvc 的…...

Matlab实现深度学习(附上完整仿真源码)

文章目录简单案例完整仿真代码下载简单案例 深度学习是一种能够自动学习和提取数据特征的机器学习方法&#xff0c;它已经在图像识别、语音识别、自然语言处理等领域取得了显著的成果。而Matlab作为一个强大的数学计算工具&#xff0c;也提供了丰富的深度学习工具箱&#xff0…...

我的谷歌书签

Form 表单 | Element Plusa Vue 3 based component library for designers and developershttps://element-plus.gitee.io/zh-CN/component/form.html#%E5%AF%B9%E9%BD%90%E6%96%B9%E5%BC%8F three.js exampleshttp://www.yanhuangxueyuan.com/threejs/examples/#software_geo…...

day3 数据库技术考点汇总

一、重点知识点 基本概念&#xff1a;三级模式-两级映像、数据库设计数据库模型&#xff1a;E-R模型、关系模型、关系代数&#xff08;结合SQL语言&#xff09;规范化&#xff1a;函数依赖、健与约束、范式、模式分解事务并发&#xff1a;并发三种问题、三级封锁协议数据库新技…...

学剪辑难吗 如何使用会声会影2023做剪辑视频

很多剪辑初学者都问过一个问题&#xff0c;学剪辑难吗&#xff1f;其实不论学什么&#xff0c;只要用心学都不难&#xff0c;今天我们就来讲讲如何学做剪辑视频&#xff0c;感兴趣的小伙伴们不要走开&#xff01;一、学剪辑难吗 其实学剪辑并不是件难事&#xff0c;但是需要掌握…...

django学习日记

1、虚拟环境 virtualenv "加虚拟环境名字" 在当前目录下创建一个虚拟环境 进入虚拟环境执行activate进入该虚拟环境&#xff0c;再执行deactivate退出虚拟环境 安装一个包来管理虚拟环境&#xff0c;每次创建虚拟环境都放到同一位置&#xff0c;以及在任意位置都可…...

在线教学视频课程如何防止学员挂机?

阿酷TONY / 2023-3-31 / 长沙 / 原创 / 要不&#xff1f;交个朋友吧&#xff1f; 在线教学视频课程如何防止学员挂机&#xff1f;siri:这是个有意思的问题哈~~~在线教育、在线企业培训机构通常是如何处理的呢&#xff1f; 答&#xff1a;在视频播放过程中&#xff0c;弹出问题…...

【Redis】安装配置

文章目录Redis简介Redis版本迭代Redis安装配置官网地址操作系统环境基础查看本地redis版本修改配置文件docker容器安装redis测试linux版Redis简介 简介 与传统数据库关系(mysql)&#xff0c;Redis是key-value数据库(NoSQL一种)&#xff0c;mysql是关系数据库。Redis数据操作主要…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

大数据学习(132)-HIve数据分析

​​​​&#x1f34b;&#x1f34b;大数据学习&#x1f34b;&#x1f34b; &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

Golang——9、反射和文件操作

反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一&#xff1a;使用Read()读取文件2.3、方式二&#xff1a;bufio读取文件2.4、方式三&#xff1a;os.ReadFile读取2.5、写…...

Ubuntu Cursor升级成v1.0

0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开&#xff0c;快捷键也不好用&#xff0c;当看到 Cursor 升级后&#xff0c;还是蛮高兴的 1. 下载 Cursor 下载地址&#xff1a;https://www.cursor.com/cn/downloads 点击下载 Linux (x64) &#xff0c;…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

抽象类和接口(全)

一、抽象类 1.概念&#xff1a;如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象&#xff0c;这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法&#xff0c;包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中&#xff0c;⼀个类如果被 abs…...