二叉树之遍历

二叉树之遍历

  • 二叉树遍历
    • 遍历分类
      • 前序遍历
        • 流程描述
        • 代码实现
      • 中序遍历
        • 流程描述
        • 代码实现
      • 后序遍历
        • 流程描述
        • 代码实现
      • 层次遍历
        • 流程描述
        • 代码实现
      • 总结

二叉树遍历

遍历分类

遍历二叉树的思路有 4 种,分别是:

  • 前序遍历二叉树,有递归和非递归两种方式;
  • 中序遍历二叉树,有递归和非递归两种方式;
  • 后序遍历二叉树,有递归和非递归两种方式;
  • 层次遍历二叉树

前序遍历

流程描述

所谓前序遍历二叉树,指的是从根结点出发,按照以下步骤访问二叉树的每个结点:

  1. 访问当前结点;
  2. 进入当前结点的左子树,以同样的步骤遍历左子树中的结点;
  3. 遍历完当前结点的左子树后,再进入它的右子树,以同样的步骤遍历右子树中的结点;

举个简单的例子,下图是一棵二叉树:
在这里插入图片描述
前序遍历这棵二叉树的过程是:

访问根节点 1;
进入 1 的左子树,执行同样的步骤:
    访问结点 2;
    进入 2 的左子树,执行同样的步骤:
        访问结点 4;
        结点 4 没有左子树;
        结点 4 没有右子树;
    进入 2 的右子树,执行同样的步骤:
        访问结点 5;
        结点 5 没有左子树;
        结点 5 没有右子树;
进入 1 的右子树,执行同样的步骤:
    访问结点 3;
    进入 3 的左子树,执行同样的步骤:
        访问结点 6;
        结点 6 没有左子树;
        结点 6 没有右子树;
    进入 3 的右子树,执行同样的步骤:
        访问结点 7;
        结点 7 没有左子树;
        结点 7 没有右子树; 

经过以上过程,就访问了二叉树中的各个结点,访问的次序是:

1 2 4 5 3 6 7

代码实现

    /**
     * 前序遍历- 递归实现
     * 访问当前结点;
     * 进入当前结点的左子树,以同样的步骤遍历左子树中的结点;
     * 遍历完当前结点的左子树后,再进入它的右子树,以同样的步骤遍历右子树中的结点;
     * @param treeNode
     */
    public static void preTraverseForRecursion(TreeNode treeNode){
        if (treeNode != null){
            // 访问当前节点
            printTreeNode(treeNode);
            // 访问当前节点的左子节点
            preTraverseForRecursion(treeNode.left);
            // 访问当前节点的右子节点
            preTraverseForRecursion(treeNode.right);
        }
    }

    /**
     * 前序遍历- 非递归实现
     * 众所周知:递归实现无非是使用了栈结构来实现的,压栈,出栈,所以非是递归实现前序遍历就是自己实现栈
     * 访问当前结点;
     * 进入当前结点的左子树,以同样的步骤遍历左子树中的结点;
     * 遍历完当前结点的左子树后,再进入它的右子树,以同样的步骤遍历右子树中的结点;
     * @param treeNode
     */
    public static void preTraverseForNoRecursion(TreeNode treeNode){
        TreeNode curr = treeNode;
        TreeNodeStack stack = new TreeNodeStack();
        while (curr != null || !stack.isEmpty()){
            if (curr != null){
                // 访问当前节点
                printTreeNode(curr);
                stack.push(curr);
                curr = curr.left;
            }else {
                TreeNode pop = stack.pop();
                curr = pop.right;
            }
        }
    }
/**
 * 树节点栈
 */
@Data
public class TreeNodeStack {
    private int top = -1;

    private TreeNode[] stack = new TreeNode[10];

    public boolean isEmpty(){
        return top < 0;
    }

    /**
     * 入栈
     * @param treeNode
     */
    public void push(TreeNode treeNode){
        top++;
        stack[top] = treeNode;
    }

    /**
     * 出栈
     * @return
     */
    public TreeNode pop(){
        if (top < 0){
            return null;
        }
        TreeNode treeNode = stack[top];
        top--;
        return treeNode;
    }
}

中序遍历

流程描述

二叉树的中序遍历,指的是从根结点出发,按照以下步骤访问二叉树中的每个结点:

  1. 先进入当前结点的左子树,以同样的步骤遍历左子树中的结点;
  2. 访问当前结点;
  3. 最后进入当前结点的右子树,以同样的步骤遍历右子树中的结点。

在这里插入图片描述
中序遍历这棵二叉树的过程是:

进入结点 1 的左子树,访问左子树中的结点;
    进入结点 2 的左子树,访问左子树中的结点;
        试图进入结点 4 的左子树,但该结点没有左子树;
        访问结点 4;
        试图进入结点 4 的右子树,但该结点没有右子树;
    访问结点 2;
    进入结点 2 的右子树,访问右子树中的结点;
        试图进入结点 5 的左子树,但该结点没有左子树;
        访问结点 5;
        试图进入结点 5 的右子树,但该结点没有右子树;
访问结点 1;
进入结点 1 的右子树,访问右子树中的结点;
    进入结点 3 的左子树,访问左子树中的结点;
        试图进入结点 6 的左子树,但该结点没有左子树;
        访问结点 6;
        试图进入结点 6 的右子树,但该结点没有右子树;
    访问结点 3;
    进入结点 3 的右子树,访问右子树中的结点;
        试图进入结点 7 的左子树,但该结点没有左子树;
        访问结点 7;
        试图进入结点 7 的右子树,但该结点没有右子树;

最终,中序遍历图 1 中的二叉树,访问各个结点的顺序是:

4 2 5 1 6 3 7

代码实现
/**
     * 中序遍历-递归实现
     * 二叉树的中序遍历,指的是从根结点出发,按照以下步骤访问二叉树中的每个结点:
     * 1. 先进入当前结点的左子树,以同样的步骤遍历左子树中的结点;
     * 2. 访问当前结点;
     * 3. 最后进入当前结点的右子树,以同样的步骤遍历右子树中的结点。
     * @param treeNode
     */
    public static void inTraverseForRecursion(TreeNode treeNode){
        if (treeNode != null){
            // 递归-当问当前节点的左子节点
            inTraverseForRecursion(treeNode.left);
            // 访问当前节点
            printTreeNode(treeNode);
            // 递归-访问当前节点的右子节点
            inTraverseForRecursion(treeNode.right);
        }
    }

    /**
     * 中序遍历-非递归实现
     * 二叉树的中序遍历,指的是从根结点出发,按照以下步骤访问二叉树中的每个结点:
     * 1. 先进入当前结点的左子树,以同样的步骤遍历左子树中的结点;
     * 2. 访问当前结点;
     * 3. 最后进入当前结点的右子树,以同样的步骤遍历右子树中的结点。
     * @param treeNode
     */
    public static void inTraverseForNoRecursion(TreeNode treeNode){
        TreeNode curr = treeNode;
        TreeNodeStack stack = new TreeNodeStack();
        while (curr != null || !stack.isEmpty()){
            if (curr != null){
                // 入栈顺序:1, 2, 4,
                stack.push(curr);
                curr = curr.left;
            }else {
                // 出栈顺序:4, 2, 1
                TreeNode pop = stack.pop();
                printTreeNode(pop);
                // 然后访问右节点
                curr = pop.right;
            }
        }
    }

后序遍历

流程描述

后序遍历二叉树,指的是从根结点出发,按照以下步骤访问树中的每个结点:

  1. 优先进入当前结点的左子树,以同样的步骤遍历左子树中的结点;
  2. 如果当前结点没有左子树,则进入它的右子树,以同样的步骤遍历右子树中的结点;
  3. 直到当前结点的左子树和右子树都遍历完后,才访问该结点。

以下图所示的二叉树为例:
在这里插入图片描述
后序遍历这棵二叉树的过程是:

从根节点 1 出发,进入该结点的左子树;
    进入结点 2 的左子树,遍历左子树中的结点:
        进入结点 4 的左子树,但该结点没有左孩子;
        进入结点 4 的右子树,但该结点没有右子树;
        访问结点 4;
    进入结点 2 的右子树,遍历右子树中的结点:
        进入结点 5 的左子树,但该结点没有左孩子;
        进入结点 5 的右子树,但该结点没有右孩子;
        访问结点 5;
    访问结点 2;
进入结点 1 的右子树,遍历右子树中的结点:
    进入结点 3 的左子树,遍历左子树中的结点:
        进入结点 6 的左子树,但该结点没有左孩子;
        进入结点 6 的右子树,但该结点没有右子树;
        访问结点 6;
    进入结点 3 的右子树,遍历右子树中的结点:
        进入结点 7 的左子树,但该结点没有左孩子;
        进入结点 7 的右子树,但该结点没有右孩子;
        访问结点 7;
    访问结点 3;
访问结点 1

最终,后序遍历图 1 中的二叉树,访问各个结点的顺序是:

4 5 2 6 7 3 1

代码实现
 /**
     * 后序遍历-递归实现
     * 后序遍历二叉树,指的是从根结点出发,按照以下步骤访问树中的每个结点:
     * 1. 优先进入当前结点的左子树,以同样的步骤遍历左子树中的结点;
     * 2. 如果当前结点没有左子树,则进入它的右子树,以同样的步骤遍历右子树中的结点;
     * 3. 直到当前结点的左子树和右子树都遍历完后,才访问该结点。
     * @param treeNode
     */
    public static void postTraverseForRecursion(TreeNode treeNode){
        if (treeNode != null){
            // 递归-当问当前节点的左子节点
            postTraverseForRecursion(treeNode.left);
            // 递归-访问当前节点的右子节点
            postTraverseForRecursion(treeNode.right);
            // 访问当前节点
            printTreeNode(treeNode);
        }
    }

    /**
     * 后序遍历-非递归实现
     * 后序遍历二叉树,指的是从根结点出发,按照以下步骤访问树中的每个结点:
     * 1. 优先进入当前结点的左子树,以同样的步骤遍历左子树中的结点;
     * 2. 如果当前结点没有左子树,则进入它的右子树,以同样的步骤遍历右子树中的结点;
     * 3. 直到当前结点的左子树和右子树都遍历完后,才访问该结点。
     *
     * 4, 5, 2, 6, 7, 3, 1
     * @param treeNode
     */
    public static void postTraverseForNoRecursion(TreeNode treeNode){
        TreeNode curr = treeNode;
        LinkedList<TreeNode> stack = new LinkedList<>();
        // 定义最后一次出栈节点,防止陷入重复执行
        TreeNode pop = null;
        while (curr != null || !stack.isEmpty()){
            if (curr != null){
                stack.push(curr);
                curr = curr.left;
            }else {
                // peek方法是查询栈顶数据,但是不弹出
                TreeNode last = stack.peek();
                // last.right == pop 如果相等,那就说明已经执行过该右子节点了,这个条件是防止有右子节点的数据陷入死循环中
                if (last.right == null || last.right == pop){
                    pop = stack.pop();
                    printTreeNode(pop);
                }else {
                    curr = last.right;
                }
            }
        }
    }

层次遍历

流程描述

在这里插入图片描述
上面这棵树一共有 3 层,根结点位于第一层,以此类推。

所谓层次遍历二叉树,就是从树的根结点开始,一层一层按照从左往右的次序依次访问树中的结点。

层次遍历用阻塞队列存储的二叉树,可以借助队列存储结构实现,具体方案是:

  1. 将根结点入队;
  2. 从队列的头部提取一个结点并访问它,将该结点的左孩子和右孩子依次入队;
  3. 重复执行第 2 步,直至队列为空;

假设将图 1 中的二叉树存储到链表中,那么层次遍历的过程是:

根结点 1 入队(1);
根结点 1 出队并访问它,然后将 1 的左孩子 2 和右孩子 3 依次入队(3, 2);
将结点 2 出队并访问它,然后将 2 的左孩子 4 和右孩子 5 依次入队(5,4,3);
将结点 3 出队并访问它,然后将 3 的左孩子 6 和右孩子 7 依次入队(7,6,5,4);
根结点 4 出队并访问它,然后将 4 的左孩子(无)和右孩子(无)依次入队(7,6,5);
将结点 5 出队并访问它,然后将 5 的左孩子(无)和右孩子(无)依次入队(7,6);
将结点 6 出队并访问它,然后将 6 的左孩子(无)和右孩子(无)依次入队(7);  
将结点 7 出队并访问它,然后将 6 的左孩子(无)和右孩子(无)依次入队();
队列为空,层次遍历结束

最终,后序遍历图 1 中的二叉树,访问各个结点的顺序是:

1 2 3 4 5 6 7

代码实现
 /**
     * 层次遍历
     * 所谓层次遍历二叉树,就是从树的根结点开始,一层一层按照从左往右的次序依次访问树中的结点。
     * 1. 将根结点入队;
     * 2. 从队列的头部提取一个结点并访问它,将该结点的左孩子和右孩子依次入队;
     * 3. 重复执行第 2 步,直至队列为空;
     * @param treeNode
     */
    public static void levelTraverseForRecursion(TreeNode treeNode){
        if (treeNode != null){
            LinkedBlockingQueue<TreeNode> queue = new LinkedBlockingQueue<>(10);
            queue.offer(treeNode);
            doPushQueue(queue);
        }
    }

    /**
     * 使用阻塞队列实现二叉树层次遍历
     * 阻塞队列的特点就是先进先出
     * @param nowQueue
     */
    private static void doPushQueue(LinkedBlockingQueue<TreeNode> nowQueue){
        if (nowQueue.isEmpty()){
            return;
        }
        // 从阻塞队列中弹出
        TreeNode poll = nowQueue.poll();
        while (poll != null){
            printTreeNode(poll);
            // 如果左子节点不为null, 则入队列
            if (poll.left != null){
                nowQueue.offer(poll.left);
            }
            // 如果右子节点不为null, 则入队列
            if (poll.right != null){
                nowQueue.offer(poll.right);
            }
            // 从阻塞队列中弹出
            poll = nowQueue.poll();
        }
    }

总结

总结各个遍历类型的流程

前序遍历:根节点 - 左节点 - 右节点
中序遍历:左节点 - 根节点 - 右节点
后序遍历:左节点 - 右节点 - 根节点
层次遍历:从根节点开始一层一层的遍历(左节点-右节点)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用dify实现简单的Agent应用(AI信息检索)

这篇文章里&#xff0c;我们来聊聊如何使用字节最新的豆包大模型&#xff0c;在 Dify 上来快速完成一个具备理解需求、自主规划、自主选择工具使用的简单智能体&#xff08;Agent&#xff09;。 准备工作 完整准备过程分为&#xff1a;准备 Docker 环境、启动 Dify 程序、启动…

线性代数基础概念:矩阵

目录 线性代数基础概念&#xff1a;矩阵 1. 矩阵的定义 2. 矩阵的运算 3. 矩阵的特殊类型 4. 矩阵的秩 5. 矩阵的初等变换 6. 矩阵的特征值与特征向量 7. 矩阵的应用 8. 矩阵总结 总结 线性代数基础概念&#xff1a;矩阵 矩阵是线性代数中的另一个重要概念&#xff0…

vue目录说明

vue目录说明 主要目录说明 .vscode - - -vscode工具的配置文件夹 node_modules - - - vue项目的运行依赖文件夹 public - - -资源文件夹&#xff08;浏览器图标&#xff09; src- - -源码文件夹 .gitignore - - -git忽略文件 index.html - - -入口html文件 package.json - - -…

windows搭建mqtt服务器,并配置DTU收集传感器数据

1.下载并安装emqx服务器 参考&#xff1a;Windows系统下本地MQTT服务器搭建&#xff08;保姆级教程&#xff09;_mqtt windows-CSDN博客 这里我下载的是emqx-5.3.0-windows-amd64.zip版本 下载好之后&#xff0c;放到服务器的路径&#xff0c;我这里放的地方是&#xff1a;C…

图像信号处理器(ISP)基础算法及处理流程

&#x1f4aa; 专业从事且热爱图像处理&#xff0c;图像处理专栏更新如下&#x1f447;&#xff1a; &#x1f4dd;《图像去噪》 &#x1f4dd;《超分辨率重建》 &#x1f4dd;《语义分割》 &#x1f4dd;《风格迁移》 &#x1f4dd;《目标检测》 &#x1f4dd;《暗光增强》 &a…

FreeRTOS之队列上锁和解锁(详解)

这篇文章将记录我学习实时操作系统FreeRTOS的队列上锁和解锁的知识&#xff0c;在此分享给大家&#xff0c;希望我的分享能给你带来不一样的收获&#xff01; 目录 一、简介 二、队列上锁函数prvLockQueue&#xff08;&#xff09; 1、函数初探 2、应用示例 三、队列解锁函…

转让北京文化传媒公司带营业性演出经纪许可证

影视文化传播倡导将健康的影视文化有效传播给观众&#xff0c;从而构建观众与电影制作者的良 性沟通与互动&#xff0c;是沟通电影制作者与电影受众的重要桥梁。影视文化泛指以电影&#xff0c;电视方式所进行的全部文化创造&#xff0c;即体现为电影&#xff0c;电视全部的存在…

找不到msvcp120.dll无法继续执行的原因分析及解决方法

在计算机使用中&#xff0c;经常会遇到msvcp120.dll文件丢失的情况&#xff0c;很多人对这个文件不是很熟悉&#xff0c;今天就来给大家讲解一下msvcp120.dll文件的丢失以及这个文件的重要性&#xff0c;让大家更好地了解计算机&#xff0c;同时也可以帮助我们更好地掌握这个文…

航模插头篇

一、常见的电池插头&#xff08;电调端 是公头 电池端 是母头&#xff09; 电池总是被插的 1.XT60头 过流大 安全系数高 难插拔 2.T插 插拔轻松 过流比较小 容易发烫 电调端 是公头 电池端 是母头 3.香蕉头插孔 过流够 插拔轻松 但 容易插反 爆炸 4.TX90(和XT60差…

2024 年 亚太赛 APMCM (A题)中文赛道国际大学生数学建模挑战赛 | 飞行器外形的优化 | 数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题&#xff01; 完整内容可以在文章末尾领取&#xff01; 第一个问…

C++内存管理(候捷)第一讲 笔记

内存分配的每一层面 applications可以调用STL&#xff0c;里面会有allocator进行内存分配&#xff1b;也可以使用C 基本工具primitives&#xff0c;比如new, new[], new(), ::operator new()&#xff1b;还可以使用更底层的malloc和free分配和释放内存。最底层的是系统调用&…

明星代言6个提升企业形象的杀手锏-华媒舍

在当今竞争激烈的商业世界中&#xff0c;企业形象的塑造对于品牌的发展至关重要。而明星代言作为一种常见的营销手段&#xff0c;被广泛使用来提升企业形象和产品销售。本文将介绍明星代言的六个杀手锏&#xff0c;帮助您了解如何通过明星代言来提升企业形象。 1. 拥有广泛的影…

十二、【源码】Spring整合AOP

源码地址&#xff1a;https://github.com/spring-projects/spring-framework 仓库地址&#xff1a;https://gitcode.net/qq_42665745/spring/-/tree/12-spring-aop Spring整合AOP 核心类&#xff1a; DefaultAdvisorAutoProxyCreator&#xff1a;用于在Spring框架中自动为符…

若依多数据源原理分析

首先&#xff0c;想明白不同的接口想要使用不同的数据源。 那么自然想到了AOP&#xff0c;自定义注解。 通过自定义注解标注当前方法到底使用的是哪个数据源。 上面是前置条件。 看下若依是怎么处理的&#xff1a; 1.定义自定义注解&#xff0c;以及对应的多数据源的枚举类…

天润融通分析AI技术助力客户服务,实现满意度三倍增长

如今&#xff0c;客户体验越来越成为影响客户决策的核心要素。 对于企业来讲&#xff0c;客户在不同触点的每一次互动体验&#xff0c;都成为塑造品牌声誉的“Aha时刻”。但同时&#xff0c;随着社会的发展的加速&#xff0c;客户的需求也在日新月异&#xff0c;给企业带来挑战…

Codeforces Round 955 (Div. 2, with prizes from NEAR!)(A~C题解)

这场比赛怎么说呢&#xff0c;一开始打的还算好&#xff0c;能进前1000&#xff0c;但是后面就被卡住了&#xff0c;这个确实没办法水平还是不够&#xff0c;学过的还是没想起来&#xff0c;后面继续练 A. Soccer 题解&#xff1a;水题一个&#xff0c;想要在过程中出现平局的…

web零碎知识

&nbsp 在html文件中 连续的空格会被认为是一个空格 所以我们需要使用&nbsp来代表空格 &#x3000 把这个当成tab键来使用 我们可以引入js文件&#xff0c;就可以减少html文件的长度。 首先创建一个js文件夹&#xff0c;然后在js文件夹中创建一个&#xff0c;后缀…

【第17章】MyBatis-Plus自动维护DDL

文章目录 前言一、功能概述二、注意事项三、代码示例四、实战1. 准备2. ddl配置类3. 程序启动4. 效果(数据库) 总结 前言 在MyBatis-Plus的3.5.3版本中&#xff0c;引入了一项强大的功能&#xff1a;数据库DDL&#xff08;数据定义语言&#xff09;表结构的自动维护。这一功能…

【电路笔记】-B类放大器

B类放大器 文章目录 B类放大器1、概述2、B类放大器介绍3、推挽式配置4、限制交叉失真5、B类放大器效率6、总结1、概述 我们在之前的文章中已经知道,A 类放大器的特点是导通角为 360,理论最大效率为 50%。 在本文中,我们将详细介绍另一类放大器,称为B类放大器,它是为解决A…

康姿百德磁性床垫好不好,效果怎么样靠谱吗

康姿百德典雅款床垫&#xff0c;打造舒适睡眠新体验 康姿百德床垫是打造舒适睡眠新体验的首选&#xff0c;其设计能够保护脊椎健康&#xff0c;舒展脊椎&#xff0c;让您享受一夜好眠。康姿百德床垫的面料选择也非常重要&#xff0c;其细腻亲肤的针织面料给您带来柔软舒适的触…