408数据结构-二叉树的遍历 自学知识点整理

前置知识:二叉树的概念、性质与存储结构


二叉树的遍历

二叉树的遍历是指按某条搜索路径访问树中每个结点,使得每个结点均被访问一次,而且仅被访问一次。
二叉树的递归特性:
①要么是棵空二叉树;
②要么就是由“根节点+左子树+右子树”组成的二叉树。
由此可知,遍历一棵二叉树只需确定对根结点 N N N、左子树 L L L和右子树 R R R的访问顺序即可。

typedef struct ElemType {int value;
}ElemType;
typedef struct BiTNode {ElemType data;struct BiTNode* lchild, * rchild;
}BiTNode, * BiTree;
BiTree root;//声明一棵二叉树

1.先序遍历( P r e O r d e r PreOrder PreOrder
若二叉树为空,则什么也不做;否则:

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

对应的递归算法如下:

void PreOrder(BiTree T) {//先序遍历(根左右)if (T != NULL) {visit(T);//访问根结点PreOrder(T->lchild);//递归访问左子树PreOrder(T->rchild);//递归访问右子树}return;
}

2.中序遍历( I n O r d e r InOrder InOrder
若二叉树为空,则什么也不做;否则:

  • 中序遍历左子树;
  • 访问根结点;
  • 中序遍历右子树。

对应的递归算法如下:

void InOrder(BiTree T) {//中序遍历(左根右)if (T != NULL) {InOrder(T->lchild);//递归访问左子树visit(T);//访问根结点InOrder(T->rchild);//递归访问右子树}return;
}

3.后序遍历( P o s t O r d e r PostOrder PostOrder
若二叉树为空,则什么也不做;否则:

  • 后序遍历左子树;
  • 后序遍历右子树。
  • 访问根结点;

对应的递归算法如下:

void PostOrder(BiTree T) {//后序遍历(左右根)if (T != NULL) {InOrder(T->lchild);//递归访问左子树InOrder(T->rchild);//递归访问右子树visit(T);//访问根结点}return;
}

e . g . 求树的深度 e.g.\text{求树的深度} e.g.求树的深度

int TreeDepty(BiTree T) {//求树的深度if (T == NULL)return 0;//若根结点为空返回0else {int l = TreeDepty(T->lchild);//递归求左子树深度int r = TreeDepty(T->rchild);//递归求右子树深度return l > r ? l + 1 : r + 1;//树的深度=max(左子树深度,右子树深度)+1}
}

模板题:洛谷P4913 【深基16.例3】二叉树深度

AC代码放在我的Github:传送门

上述三种遍历算法中,递归遍历左、右子树的顺序都是固定的,区别只是访问根结点的顺序不同。不管采用哪种方式每个结点都访问一次且仅访问一次,所以时间复杂度都是 O ( n ) O(n) O(n)。在递归遍历中,递归工作栈的栈深恰好为树的深度。


模板题:【洛谷B3642】二叉树的遍历

题目描述

有一个 n ( n ≤ 1 0 6 ) n(n \le 10^6) n(n106) 个结点的二叉树。给出每个结点的两个子结点编号(均不超过 n n n),建立一棵二叉树(根节点的编号为 1 1 1),如果是叶子结点,则输入 0 0

建好树这棵二叉树之后,依次求出它的前序、中序、后序列遍历。

输入格式

第一行一个整数 n n n,表示结点数。

之后 n n n 行,第 i i i 行两个整数 l l l r r r,分别表示结点 i i i 的左右子结点编号。若 l = 0 l=0 l=0 则表示无左子结点, r = 0 r=0 r=0 同理。

输出格式

输出三行,每行 n n n 个数字,用空格隔开。

第一行是这个二叉树的前序遍历。

第二行是这个二叉树的中序遍历。

第三行是这个二叉树的后序遍历。

样例 #1

样例输入 #1

7
2 7
4 0
0 0
0 3
0 0
0 5
6 0

样例输出 #1

1 2 4 3 7 6 5
4 3 2 1 6 5 7
3 4 2 5 6 7 1

AC代码如下:(递归实现)

#define _CRT_SECURE_NO_WARNINGS 1
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;const int MAXN = 1e6 + 7;
struct TreeNode {int lchild = 0, rchild = 0;
}t[MAXN];
int n;inline void Visit(int p) {cout << p << " ";return;
}inline void PreOrder(int p) {if (!p)return;Visit(p);PreOrder(t[p].lchild);PreOrder(t[p].rchild);return;
}inline void InOrder(int p) {if (!p)return;InOrder(t[p].lchild);Visit(p);InOrder(t[p].rchild);return;
}inline void PostOrder(int p) {if (!p)return;PostOrder(t[p].lchild);PostOrder(t[p].rchild);Visit(p);return;
}int main() {cin >> n;int l, r;for (int i = 1; i <= n; ++i) {cin >> l >> r;t[i].lchild = l, t[i].rchild = r;}PreOrder(1);cout << endl;InOrder(1);cout << endl;PostOrder(1);cout << endl;return 0;
}

408考研初试中,对非递归算法的要求通常不高。但是该学的还是要学的。

※递归算法和非递归算法的转换

中序遍历为例,由栈的思想分析其过程:

①沿着根的左孩子,依次入栈,直到左孩子为空,说明此时已找到可以输出的结点。
②栈顶元素出栈并访问:若其右孩子为空,继续执行②;否则对其右子树执行①。
(图片来自王道408数据结构考研复习指导2025)图片来自王道408数据结构考研复习指导2025

图中二叉树的中序遍历序列为: D B E A C DBEAC DBEAC
由分析可写出代码:

typedef struct LNode {//单链表存储栈struct BiTNode* Node;//存放二叉树结点struct LNode* next;
}LNode,*LinkList;void InitLink(LinkList & S) {//初始化栈S = (LinkList)malloc(sizeof(LNode));if (S == NULL)return;S->next = NULL;return;
}bool Is_Empty(LinkList S) {//判栈空return S->next == NULL ? true : false;
}void Push(LinkList& S, BiTree p) {//入栈LNode* q = (LNode*)malloc(sizeof(LNode));q->Node = p;q->next = S->next;S->next = q;return;
}void Pop(LinkList& S, BiTree& p) {//出栈if (Is_Empty(S))return;LNode* q = S->next;p = q->Node;S->next = q->next;free(q);return;
}void InOrder2(BiTree T) {//中序遍历二叉树的非递归算法LinkList S;//声明栈InitLink(S);//初始化栈BiTree p = T;//遍历指针pwhile (p || !Is_Empty(S)) {//栈不空或p不空时循环if (p) {//一路向左
//			visit(p);//若为前序遍历,则在此访问先当前结点,并入栈Push(S, p);//当前结点入栈p = p->lchild;//左孩子不空则一直向左走}else {//出栈,并转向出栈结点的右孩子Pop(S, p);//栈顶元素出栈visit(p);//访问出栈结点p = p->rchild;//走向右子树,p赋值为当前结点的右孩子}//返回循环}return;
}

完整代码可看我的Github:传送门

后序遍历的非递归实现是最难的,因为在后序遍历中要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问,才能访问根结点。
后序遍历算法的思路分析:从根节点开始,将其入栈,然后沿其左子树一直向下搜索,直到搜索到没有左孩子的结点,但是此时不能出栈并访问,因为若其有右子树,则还需按相同的规则对其右子树进行处理。直至上述操作进行不下去为止,此时若栈顶元素想要出栈并被访问,要么右子树为空,要么右子树刚被访问完(此时左子树早已访问完),这样才能保证正确的访问顺序。
这一部分内容并不重要,可跳过。


二叉树的层次遍历

对二叉树进行按层次的遍历,需要借助一个辅助队列实现,具体思想如下:
①首先将二叉树的根结点入队。
②若队列非空,则队头结点出队,并访问该结点。若该结点有左孩子,则将其左孩子入队;若该结点有右孩子,则将其右孩子入队。
③重复步骤②,直至队列为空。

需要的前置知识:队列的链式存储结构

预处理如下:

typedef struct ElemType {int value;
}ElemType;
typedef struct BiTNode {ElemType data;struct BiTNode* lchild, * rchild;
}BiTNode, * BiTree;
BiTree root;//声明一棵二叉树void Visit(BiTree p) {cout << p->data.value << " ";return;
}typedef struct LinkNode {//单链表BiTree Node;//数据域存放二叉树结点struct LinkNode* next;
}LinkNode, * LinkList;
typedef struct {//链队列LinkNode* front, * rear;
}LinkQueue;void InitQueue(LinkQueue& Q) {//队列初始化(默认带头结点)Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));Q.front->next = NULL;return;
}bool Is_Empty(LinkQueue Q) {//队列判空return Q.front == Q.rear ? true : false;
}void EnQueue(LinkQueue& Q, BiTree T) {//入队LinkNode* p = (LinkNode*)malloc(sizeof(LinkNode));if (p == NULL)return;p->Node = T;p->next = NULL;Q.rear->next = p;Q.rear = p;return;
}void DeQueue(LinkQueue& Q, BiTree& p) {//出队if (Is_Empty(Q))return;LinkNode* h = Q.front->next;p = h->Node;Q.front->next = h->next;if (Q.rear == h)Q.rear = Q.front;free(h);return;
}

二叉树的层次遍历实现如下:

void LevelOrder(BiTree T) {//二叉树层次遍历LinkQueue Q;InitQueue(Q);//初始化辅助队列BiTree p;EnQueue(Q, T);//根结点入队while (!Is_Empty(Q)) {//当队列不为空时DeQueue(Q, p);//队头结点出队Visit(p);//访问队头结点if (p->lchild != NULL)EnQueue(Q, p->lchild);//若左孩子不为空,则左孩子入队if (p->rchild != NULL)EnQueue(Q, p->rchild);//若右孩子不为空,则右孩子入队}return;
}

完整代码可以看我的GitHub:传送门

上述二叉树层次遍历的算法可作为模板学习,可通过练习加深对程序执行过程的理解,从而熟练掌握并能手写出来。

由遍历序列构造二叉树

(这一部分没有对代码实现要求,会手推即可。)
对一棵给定的二叉树,其先序序列、中序序列、后序序列和层序序列都是确定的。但是,只给出四种遍历序列中的任意一种,是无法确定一棵二叉树的。若已知中序序列,再给出其他三种遍历序列中的任意一种,就可以唯一地确定一棵二叉树。

1. 由中序序列和先序序列构造一棵二叉树

先序序列中,第一个结点一定是二叉树的根结点;而中序序列中,根结点必然将中序序列分割成两个子序列,前一个子序列是根的左子树的中序序列,后一个子序列是根的右子树的中序序列。
根的左子树的先序序列和中序序列长度是一样的,可以在先序序列的第一位中找到根的左子树的根结点,再将根的左子树的中序序列分割成两个子序列处理。同理,根的右子树也可如此操作。
将整个二叉树递归分解下去,最终就能唯一地确定这棵二叉树。
分解到最后剩下两个或三个结点时,即可通过中序(左根右)和先序(根左右)的规则进行验证。

2. 由中序序列和后序序列构造一棵二叉树

后序序列的最后一个结点一定是二叉树的根结点,按照先序序列同样的思想,根结点可以将中序序列分割成两个子序列。递归分解下去,最后也能唯一地确定这棵二叉树。
分解到最后剩下两个或三个结点时,即可通过中序(左根右)和后序(左右根)的规则进行验证。

3.由中序序列和层序序列构造一棵二叉树

层序序列中,第一个结点一定是二叉树的根结点,这样又可以把中序序列分割成左子树的中序序列和右子树的中序序列。若存在左子树,层序序列的第二个结点一定是左子树的根,之后可对左子树再进一步划分;若存在右子树,层序序列中紧接着的下一个结点就一定是右子树的根,之后又可对右子树再进一步划分。
分解到最后剩下两个或三个结点时,即可通过中序(左根右)和层序遍历的规则进行验证。

需要注意的是,先序序列、后序序列和层序序列任意两辆组合,都无法唯一确定一棵二叉树。
例如: B ← A B←A BA B B B A A A的左孩子), A → B A→B AB B B B A A A的右孩子)
这样的两棵二叉树,其先序序列均为 A B AB AB,后序序列均为 B A BA BA,层序序列均为 A B AB AB


遍历是二叉树各种操作的基础,例如求结点的双亲,求结点的孩子,求二叉树的深度,求叶结点的个数,判断两棵二叉树是否相等,等等问题,都是在遍历的过程中进行的。因此必须掌握二叉树的各种遍历过程,并能灵活运用以解决各种问题。

以上。

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

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

相关文章

【NOI】C++程序结构入门之分支结构二

文章目录 前言一、逻辑运算符1.导入2.逻辑与&#xff08;&&&#xff09;3.逻辑或&#xff08;||&#xff09;4.逻辑非&#xff08;!&#xff09; 二、例题讲解问题&#xff1a;1656. 是两位的偶数吗问题&#xff1a;1658. 游乐设施问题&#xff1a;1659. 是否含有数字5…

AI绘画:Stable Diffusion 拒绝一眼塑料味的AI质感,超写实人物图片如何制作?简单几步教会你!

今天给大家介绍一款能够对生成的人像进行皮肤调节的 lora。 上面两幅图片的生成参数一样&#xff0c;尺寸也一样&#xff0c;但右边一幅图片相较于左面图片的画面质感&#xff0c;特别是人像皮肤的质感上有很大的提升&#xff0c;看上去更加细腻有层感。 这就是我们今天要介绍…

linux下的调试工具gdb的详细使用介绍

在之前学习中我们使用的通常是集各种功能于一体的编译器&#xff0c;例如VS stdio&#xff0c;但是一个程序在编辑后还要进行编译&#xff0c;然后才能产生一个二进制的可执行文件&#xff0c;编辑和翻译工作都可以使用不同的软件进行&#xff0c;例如记事本就是一款编辑软件&a…

03.配置监控一台服务器主机

配置监控一台服务器主机 安装zabbix-agent rpm -ivh https://mirror.tuna.tsinghua.edu.cn/zabbix/zabbix/4.0/rhel/7/x86_64/zabbix-agent-4.0.11-1.el7.x86_64.rpm配置zabbix-agent,配置的IP地址是zabbix-server的地址&#xff0c;因为要监控这台主机 vim /etc/zabbix/zab…

免费开源线上线下交友社交圈子系统 小程序+APP+H5 可支持二开!

为什么要玩社交软件&#xff1a;互联网社交软件的独特优势 首先&#xff0c;社交软件为我们提供了一个便捷的沟通方式。在传统的交往方式中&#xff0c;人们需要面对面交流&#xff0c;这种方式在时间和空间上都受到限制。而社交软件打破了这些限制&#xff0c;无论我们身处何地…

既能自动仿写公众号爆文,还能批量帮你上架闲鱼商品,打造自己的数字员工,简直yyds

「想象一下&#xff0c;如果有一个机器人在你的计算机上24小时不间断地工作&#xff0c;会不会做梦都笑着」 一、RPA机器人是什么&#xff1f; RPA——机器人流程自动化&#xff0c;它可以帮助人们完成重复性的、繁琐的工作&#xff0c;比如数据输入、网页爬取、自动化流程等…

llama3 史上最强开源大模型,赶超GTP-4,逼宫OpenAI

2024年4月18日&#xff0c;Meta公司推出了开源大语言模型Llama系列的最新产品—Llama 3&#xff0c;包含了80亿参数的Llama 3 8B和700亿参数的Llama 3 70B两个版本。Meta称其为“迄今为止最强的开源大模型”。 怪兽级性能 LLaMA3 提供了不同参数规模的版本&#xff0c;以适应…

你真的知道Show Master Status吗?

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 你真的知道Show Master Status吗&#xff1f; 前言输出字段展示file详解Position详解Binlog_Do_DBBinlog_Ignore_DBExecuted_Gtid_Set 前言 在数据库的世界里&#xff0c;每一个字段都像是一个谜团&a…

微服务---gateway网关

目录 gateway作用 gateway使用 添加依赖 配置yml文件 自定义过滤器 nacos上的gateway的配置文件 我们现在知道了通过nacos注册服务&#xff0c;通过feign实现服务间接口的调用&#xff0c;那对于不同权限的用户访问同一个接口&#xff0c;我们怎么知道他是否具有访问的权…

Verilog中求两个数的差值

根据输入信号a,b的大小关系&#xff0c;求解两个数的差值&#xff1a;输入信号a,b为8bit位宽的无符号数。如果a>b&#xff0c;则输出a-b&#xff0c;如果a≤b&#xff0c;则输出b-a。 接口信号图如下&#xff1a; 代码如下&#xff1a; &#xff08;CSDN代码块不支持Veril…

WPF之绑定属性值转换

1&#xff0c;使用Binding.Format属性简易设置绑定的属性数据显示格式。 <TextBox Grid.Row"2" Grid.Column"1"><TextBox.Text><Binding Path"UnitCost" StringFormat"{}{0:C3}" > …

加州大学欧文分校英语中级语法专项课程02:Adjectives and Adjective Clauses 学习笔记

Adjectives and Adjective Clauses course certificate 本文是 https://www.coursera.org/learn/adjective-clauses 这门课的学习笔记。 文章目录 Adjectives and Adjective ClausesWeek 01: Adjectives and Adjective PhrasesLearning Objectives Adjectives Introduction Le…

一个新细节,Go 1.17 将允许切片转换为数组指针!

在 Go 语言中&#xff0c;一个切片&#xff08;slice&#xff09;包含了对其支持数组的引用&#xff0c;无论这个数组是作为一个独立的变量存在于某个地方&#xff0c;还是仅仅是一个为支持分片而分配的匿名数组。 其切片基本结构都如下&#xff1a; // runtime/slice.go typ…

ChatGPT 4.0 直接用 !!!Code Copilot编程大模型、DALL-E AI绘图、绘制流程图、上传文件

嗨&#xff0c;你好呀&#xff0c;我是哪吒。 这一年最让人揪心的热点&#xff0c;就是各种层出不穷的AI技术。 原以为它只是短暂霸屏&#xff0c;但现实却赤裸裸展示了&#xff0c;什么叫AI抢走你的饭碗&#xff0c;连招呼都不打一声! 什么策划方案、公众号文案、营销卖点、…

学术咸鱼入门指南(1)

学术基础素养 一.巧用工具管理文献&#xff0c;形成自己的文献体系 养成习惯的第一步&#xff1a;文献命名 当你下载完一篇文献&#xff0c;应该做的第一步就是给文献重新命名。命名的逻辑可以随自己的喜好来&#xff0c;可以按照“发表年份作者文章标题”。 也可以使用文献…

利用策略模式+模板方法实现项目中运维功能

前段时间项目中有个需求&#xff1a;实现某业务的运维功能&#xff0c;主要是对10张数据库表的增删改查&#xff0c;没有复杂的业务逻辑&#xff0c;只是满足运维人员的基本需要&#xff0c;方便他们快速分析定位问题。这里简单记录分享下实现方案&#xff0c;仅供参考。 一、…

多模态路径:利用其他模态的无关数据改进变压器(CVPR 2024)

<Multimodal Pathway: Improve Transformers with Irrelevant Data from Other Modalities> 论文地址&#xff1a;https://arxiv.org/abs/2401.14405 项目网页&#xff1a;https://ailab-cvc.github.io/M2PT/ 开源代码&#xff1a;https://github.com/AILab-CVC/M2PT 讲…

vue快速入门(五十七) 作用域插槽

注释很详细&#xff0c;直接上代码 上一篇 新增内容 作用域插槽实现表格删除数据 源码 App.vue <template><div id"app"><!-- 向子组件传值 --><MyTable :tableData"tableData"><!-- 接收子组件的传值&#xff0c;默认是对象格…

Web3 ETF的基本概念

Web3 ETF&#xff08;Exchange-Traded Fund&#xff0c;交易所交易基金&#xff09;是一种投资工具&#xff0c;它允许投资者通过购买单一的基金份额来获得对多个与Web3技术相关的公司和资产的曝光。Web3技术通常指的是基于区块链构建的去中心化网络和应用&#xff0c;包括加密…

时间复杂度空间复杂度 力扣:转轮数组,消失的数字

1. 算法效率 如何衡量一个算法的好坏&#xff1f;一般是从时间和空间的维度来讨论复杂度&#xff0c;但是现在由于计算机行业发展迅速&#xff0c;所以现在并不怎么在乎空间复杂度了下面例子中&#xff0c;斐波那契看上去很简洁&#xff0c;但是复杂度未必如此 long long Fib…