(浙大陈越版)数据结构 第三章 树(上) 3.3 二叉树的遍历

news2024/11/26 8:50:05

目录

3.3.1 遍历(先中后)

二叉树的遍历

先序遍历:

中序遍历

后序遍历

tips:

3.3.2 中序非递归遍历

非递归算法实现的基本思路:使用堆栈

中序遍历的非递归算法具体实现方法为:

3.3.3 层序遍历

难点

解决方法:

队列实现

思路

有如下二叉树作为例子:

遍历过程:(出队即printf)

思考:

3.3.4 遍历应用例子

1. 输出二叉树中的叶子结点

2. 求二叉树高度

3. 二元运算表达式树及其遍历

4. 由两种遍历序列确定二叉树


3.3.1 遍历(先中后)

二叉树的遍历

先序遍历:

遍历过程:

  1. 访问根结点
  2. 先序遍历其左子树(递归遍历,根结点左子树如果也有左右子树也要遍历)
  3. 先序遍历其右子树

既然需要递归遍历左右子树,我们设想以下如果直接用递归实现这个算法(伪码):

void PreOrderTraversal( BinTree BT )
{
     if( BT ) {
         printf(“%d”, BT->Data);
         PreOrderTraversal( BT->Left );
         PreOrderTraversal( BT->Right );
     }
}

若有二叉树为A(B(DF(E))C(G(H)I)),其先序遍历访问顺序为——A-B-D-F-E-C-G-H-I

中序遍历

遍历过程:

  1. 先序遍历其左子树
  2. 访问根结点
  3. 先序遍历其右子树

代码就直接将Printf放到两次递归中间即可

void PreOrderTraversal( BinTree BT )
{
     if( BT ) {
         PreOrderTraversal( BT->Left );
         printf(“%d”, BT->Data);
         PreOrderTraversal( BT->Right );
     }
}

后序遍历

遍历过程:

  1. 先序遍历其右子树
  2. 访问根结点
  3. 先序遍历其左子树
void PostOrderTraversal( BinTree BT )
{
    if( BT ) {
        PostOrderTraversal( BT->Left );
        PostOrderTraversal( BT->Right);
        printf(“%d”, BT->Data);
    }
}

 

tips:

先序、中序和后序遍历过程:遍历过程中经过结点的路线一 样,只是访问各结点的时机不同。

3.3.2 中序非递归遍历

非递归算法实现的基本思路:使用堆栈

中序遍历是按左、根、右的顺序,所以以上一节的树为例:

  1. 在一开始遇到A,A有左子树,所以将A放入堆栈
  2. 下一个要处理的是A的左子树B,B也有左子树,所以再把B放到堆栈里
  3. 下一个是B的左子树D,D没有孩子,D放入堆栈后就要出栈了,随后B出栈
  4. 此时B这棵树左、根都遍历完了,接下来处理右子树F。
  5. F有左子树,所以先把F放入堆栈
  6. F的左子树E没有孩子,E入栈然后出栈F再出栈,最后A的左子树处理完毕,A也出栈
  7. 此时遍历顺序为DBEFA,接着处理A的右子树
  8. A的右子树C,C有左子树,存入栈,处理其左子树
  9. G没有左子树,所以先入栈后出栈
  10. G有右子树H(中序遍历顺序),H没有左子树,入栈后紧接着出栈
  11. 处理完C的左子树,C可以出栈
  12. C有右子树I,I没有左子树,入栈后紧接着出栈
  13. 最后遍历顺序即为:DBEFAGHCI

中序遍历的非递归算法具体实现方法为:

  • 遇到一个结点就将其压入栈,并遍历其左子树(若有左子树的话),
  • 当左子树遍历完毕,就从栈顶弹出这个结点并访问它
  • 按结点右指针去遍历这个结点的右子树
void InOrderTraversal( BinTree BT )
{
    BinTree T=BT;
    Stack S = CreatStack( MaxSize ); /*创建并初始化堆栈S*/
    
    while( T || !IsEmpty(S) ){
        while(T){
            /*一直向左并将沿途结点压入堆栈*/
            Push(S,T);
            T = T->Left;
        }
        if(!IsEmpty(S)){
        T = Pop(S); /*结点弹出堆栈*/
        printf(“%5d”, T->Data); /*(访问)打印结点*/
        T = T->Right; /*转向右子树*/
        }
    }
}

也可推知,先序遍历也能用非递归实现,修改printf位置即可

void PreOrderTraversal( BinTree BT )
{
    BinTree T=BT;
    Stack S = CreatStack( MaxSize ); /*创建并初始化堆栈S*/
    
    while( T || !IsEmpty(S) ){
        while(T){
            /*一直向左并将沿途结点压入堆栈*/
            Push(S,T);
            printf(“%5d”, T->Data); /*第一次碰到就打印*/
            T = T->Left;
        }
        if(!IsEmpty(S)){

        T = Pop(S); /*结点弹出堆栈*/
        T = T->Right; /*转向右子树*/
        }
    }
}

但后序遍历不能简单挪动位置实现,因为后序遍历在操作完左右子树后需要返回到根结点,而且是在第三次经过根结点时将其pop出去,所以实现它需要用到两个堆栈

1. 初始化两个堆栈s1和s2,将根节点压入s1中。
2. 从s1中弹出栈顶节点,将其压入s2中。
3. 如果该节点有左子节点,则将左子节点压入s1中。
4. 如果该节点有右子节点,则将右子节点压入s1中。
5. 重复步骤2到步骤4,直到s1为空。
6. 从s2中依次弹出节点并输出,即可得到后序遍历序列。

void PostOrder(BinTree BT)
{
    BinTree T = BT;
    stack S = createstack();
    while(T || IsEmpty(S)){
        while(T){
            push(S, T); 
            T = T->Left;
        }
        if(top(S)->Right != NULL){
            T = top(S)->Right;
            continue;
        }
        T = pop(S);
        printf("%5d", T->data);
        while(top(S)->Right == T){
            T = pop(S);
            printf("%5d", T->data);
         }
        T = top(S)->Right;
}

3.3.3 层序遍历

难点

除了先中后序,我们还有一种遍历方式是层序遍历。二叉树是一个二维结构,而遍历的序列是一个线性结构,遍历二叉树的本质是将一个二维结构变成一维线性结构。

而难点在于,只有通过父结点才能访问到左右孩子结点,再通过子结点来访问子结点的左右孩子结点,因此如果变成线性结构,线性就意味着一个点只与另一个点有关,而子结点会有两个,当访问了一个子结点之后另一个子结点该怎么处理?

解决方法:

用一个存储结构来保存暂时不访问的结点。如堆栈、队列

队列实现

思路

遍历从根结点开始,首先将根结点入队,然后开始执行循环:

  • 从队列中取出一个元素
  • 访问该元素所指结点
  • 若该元素所指结点的左、右孩子结点非空, 则将其左、右孩子的指针顺序入队。

有如下二叉树作为例子:

遍历过程:(出队即printf)

A入队,第一个元素为A,队列为A

A出队,将A的左右儿子BC入队,此时第一个元素为B,队列为BC

B出队,将B的左右儿子DF入队,此时第一个元素为C,队列为CDF

C出队,将C的左右儿子GI入队,此时第一个元素是D,队列为DFGI

D出队,D没有左右儿子,此时第一个元素是F,队列为FGI

F出队,将F的左儿子E入队,此时第一个元素是G,队列为GIE

G出队,将G的右儿子H入队,此时第一个元素是I,队列为IEH

I出队,EH都没有子结点,依次出队

出队顺序为:A B C D F G I E H,序列恰好是每一层的结点顺序

void LevelOrderTraversal ( BinTree BT )
{
    Queue Q;
    BinTree T;

    if ( !BT ) return; /* 若是空树则直接返回 */
    Q = CreatQueue( MaxSize ); /*创建并初始化队列Q*/
    AddQ( Q, BT );

    while ( !IsEmptyQ( Q ) ) {
        //Step1
        T = DeleteQ( Q );
        //step2
        printf(“%d\n”, T->Data); /*访问取出队列的结点*/
        //Step3
        if ( T->Left ) AddQ( Q, T->Left );

        if ( T->Right ) AddQ( Q, T->Right );
    }
}

思考:

若将层序遍历中的队列实现修改为堆栈实现,是否也是一种遍历方法?

是的,改为堆栈后一般称其为深度优先遍历,简单来说就是访问根结点,再依次访问其左右子树直到遍历完整棵树。

3.3.4 遍历应用例子

1. 输出二叉树中的叶子结点

思路:在遍历算法中加入一个“检测该结点左右子树是否都为空”,即可得知是否为叶子结点

//以前序遍历为例
void PreOrderPrintLeaves( BinTree BT )
{
    if( BT ) {
        //若左右子树为空,则打印结点
        if ( !BT-Left && !BT->Right )
        {
            printf(“%d”, BT->Data );
        }
        PreOrderPrintLeaves ( BT->Left );
        PreOrderPrintLeaves ( BT->Right );
    }
}

2. 求二叉树高度

二叉树的总高度=左右子树的最大高度+1,因此要求二叉树总高度,需要先知道左右子树的高度。后序遍历的遍历顺序显然很符合这样的算法

int PostOrderGetHeight( BinTree BT )
{
    int HL, HR, MaxH;

    if( BT ) {
    HL = PostOrderGetHeight(BT->Left); /*求左子树的深度*/
    HR = PostOrderGetHeight(BT->Right); /*求右子树的深度*/
    MaxH = (HL > HR)? HL : HR; /*取左右子树较大的深度*/
    return ( MaxH + 1 ); /*返回树的深度*/
    }
    else return 0; /* 空树深度为0 */
}

3. 二元运算表达式树及其遍历

如下表达式树,其作用是对左右两颗树做根结点运算

4. 由两种遍历序列确定二叉树

若已知三种遍历中的任意两种遍历序列,能否唯一确定一颗二叉树呢?

答案是不能,必须要知道中序遍历+其他任意遍历序列才行

若已知先序和中序遍历序列,如何确定一颗二叉树?

思路:已知先序第一个结点是根结点,只要在中序中找到一样的结点,就能在中序遍历序列中分割开左子树和右子树,再递归使用这个方法分解全部结点。

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

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

相关文章

经典神经网络(5)GoogLeNet及其在Fashion-MNIST数据集上的应用

经典神经网络(5)GoogLeNet及其在Fashion-MNIST数据集上的应用 1 Inception V1 的简述 Inception 网络是卷积神经网络的一个重要里程碑。在Inception 之前,大部分流行的卷积神经网络仅仅是把卷积层堆叠得越来越多,使得网络越来越深。这使得网络越来越复杂…

计算机组成原理-中央处理器-控制器功能和原理

目录 一、硬布线控制器 二、硬布线控制器的设计(硬件) 2.1分析每个阶段的微操作序列(取址、间址、执行、中断) 2.2选择cpu的控制方式 2.3 安排微操作时序 2.4电路设计 2.4.1列出操作时间表 2.4.2 写出微操作命令的最简表达式 2.4.3画出电路图 *三、微程序控制器基本原理 四…

剪映 自动打关键帧 AutoHotkey

牙叔教程 简单易懂 明确目的 做小说推文的话, 前面几分钟肯定要自己打关键帧, 所以这里的自动打关键帧指的是后面几分钟的图片, 对关键帧要求比较高的同学可以划走了, 因为这里介绍的是简单的 上上下下缩放的关键帧 要求 用剪映提取字幕…

【Python Twisted】零基础也能轻松掌握的学习路线与参考资料

Python Twisted是一个用于网络编程的事件驱动的框架,该框架使用异步I/O模型和回调函数。它支持多种协议,包括TCP、UDP、SSL/TLS、XMPP等,可以用来编写Web服务器、聊天应用、邮件服务器等。Twisted是一个成熟的框架,拥有强大的社区…

【JavaEE】文件操作和IO

目录 1、认识文件 1.1、路径 1.1.1 、绝对路径 1.1.2、相对路径 1.2、文本文件 vs 二进制文件 2、文件系统操作 3、文件内容操作 3.1、字节流用来输入的抽象类InputStream的方法使用 3.1.1、FileInputStream类的构造方法 3.1.2、字节流读操作 3.1.3、字节流写操作 3.…

Presto从入门到精通以及案例实操系列

1、简介 1.1、Presto的由来 Presto最初由Facebook公司开发,旨在解决Facebook内部大规模数据处理和数据分析的问题。在传统的Hadoop生态圈中,MapReduce作为数据处理框架,虽然能够处理海量数据,但是其查询性能却比较低下&#xff…

《面试1v1》CountDownLatch和CyclicBarrier

我是 javapub,一名 Markdown 程序员从👨‍💻,八股文种子选手。 面试官: 你用过 CountDownLatch 和 CyclicBarrier 吗? 候选人: 当然可以。CountDownLatch 和 CyclicBarrier 都是 Java 中用于多…

通过 docker-compose 快速部署 MySQL保姆级教程

文章目录 一、概述二、前期准备1)部署 docker2)部署 docker-compose 三、创建网络四、MySQL 编排部署1)构建镜像 Dockerfile2)配置文件2)编排 docker-compose.yaml3)开始部署 五、简单测试验证六、常用的 M…

在线排查内存泄漏的步骤

一、在线排查内存泄漏的步骤 想到内存泄漏问题的排查,很多开发会想到使用 Valgrind。使用 Valgrind 有几个局限: 需要安装 Valgrind 需要启停服务进程 影响服务进程性能 依赖于测试用例覆盖到 BUG 分支 由于这些原因,线上内存泄露问题并…

位图、布隆过滤器、海量数据处理

提示: 本文介绍了,位图、布隆过滤器、以及海量数据处理问题。 本节有很多关于大数处理的案例(已解答)。 ——细雨斜风作晓寒,淡烟疏柳媚晴滩。(苏轼) 文章目录 一、位图1.1 位图概念1.2 位图实…

深度学习12—VGG19实现

目录 VGG19实现 1.为数据打标签的generate_txt.py 2.对图像进行预处理的data_process.py 3.VGG19的网络构建代码net_VGG19.py 4.训练得到pth模型参数文件的get_pth_file.py 5.预测代码predict.py 6.预测VGG16与VGG19结果对比 VGG19实现 1.为数据打标签的generate_txt.p…

【git教程】

这里写目录标题 git是什么集中式版本控制系统和分布式版本控制系统git的优势git能做什么(常用)基础教程流程图介绍小节 常用Git命令速查表详解1、HEAD2、add3、commit4、branch5、merge6、rebasemerge和rebase区别7、reset8、revertrevert与reset的区别 git是什么 git是目前世…

【Java算法题】剑指offer_数据结构之02树

前言 刷题链接: https://www.nowcoder.com/exam/oj/ta?page2&tpId13&type265 2. 树 JZ55 二叉树的深度 思路:dep max_deepth(left,right)1,二叉树的深度为根节点到叶子节点,使用递归访问根节点的左孩子和右孩子&…

想要让数据更生动?试试这5种图表工具

在当今大数据时代,数据的利用和分析在各个领域的工作中起着重要的作用。因此,数据可视化图形工具已经成为数据分析的好帮手。事实上,数据可视化的本质是视觉对话。它通过图形手段清晰直观地表达信息,从数据中获得价值。然而&#…

Netty实战(九)

单元测试 一、什么是单元测试二、EmbeddedChannel 概述三、 使用 EmbeddedChannel 测试 ChannelHandler3.1 测试入站消息3.2 测试出站消息 一、什么是单元测试 单元测试的基本思想是:以尽可能小的区块测试代码,并且尽可能地和其他的代码模块以及运行时的…

Java: IO流

1.定义 IO流:存储和读取数据的解决方案 用于读写文件中的数据(可以读写文件,或网络中的数据...) 2.IO流的分类 1.按着流的方向 1.输入流:读取 2.输出流:写出 2.按照操作文件类型 1.字节流:所有类型文件 体系&…

Redis:缓存击穿、缓存穿透与缓存雪崩的区别、解决方案

0、前言 近期学习redis相关原理,记录一下开发过程中Redis的一些常见问题及应对方法。 1、缓存穿透 一句话总结:先查redis发现没数据,再去数据库查发现还是没数据。 这种情况下缓存永远不会生效,数据库将承担巨大压力。 我们知道&…

前端食堂技术周刊第 84 期:第 96 届 TC39 会议、Deno 五周年、JavaScript 安全最佳实践、2023 Node.js 性能现状

By Midjournery 美味值:🌟🌟🌟🌟🌟 口味:葡萄冰萃美式 食堂技术周刊仓库地址:https://github.com/Geekhyt/weekly 本期摘要 第 96 届 TC39 会议Deno 五周年JavaScript 安全最佳…

FreeRTOS:信号量

目录 一、信号量是什么二、二值信号量2.1二值信号量简介2.2创建二值信号量2.2.1函数 vSemaphoreCreateBinary()2.2.2函数xSemaphoreCreateBinary()2.2.3 函数 xSemephroeCreateBinaryStatic()2.2.4二值信号量创建过程分析 2.3释放信号量2.3.1函数 xSemaphoreGive ()2.3.2函数 x…

【MySQL学习6:多行输入函数——聚合函数及SQL书写和执行规则】

之前做的笔记都在有道云,之后会一点点将以前的笔记分享出来~ (配图在笔记中查看) MySQL学习6:多行输入函数——聚合函数及SQL书写和执行规则 SQL书写顺序:SQL99执行顺序:一、常见的聚合函数1. 常见的聚合函…