二叉树之遍历

news2024/12/23 13:13:45

二叉树之遍历

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

二叉树遍历

遍历分类

遍历二叉树的思路有 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.coloradmin.cn/o/1893858.html

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

相关文章

CICD流水线-父子项目打包发布至私仓库

一、方法一 在不需要发布至私仓的模块&#xff08;不需要发布的子项目&#xff09;上添加如下代码&#xff1a; <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-deploy-plugin</artifactId><configuration><s…

ll命令在ubuntu下不能使用的解决方案

ll命令在ubuntu下不能使用的解决方案 问题&#xff1a; ll命令在ubuntu下不能使用&#xff0c; 在Ubuntu终端里执行ll,提示:command not found 解决方案&#xff1a; 打开当前用户目录下的.bashrc文件 找到下面的内容&#xff0c;将前面的“#”去掉 #alias llls -alF 然…

鸿蒙开发HarmonyOS NEXT (三) 熟悉ArkTs

一、自定义组件 1、自定义组件 自定义组件&#xff0c;最基础的结构如下&#xff1a; Component struct Header {build() {} } 提取头部标题部分的代码&#xff0c;写成自定义组件。 1、新建ArkTs文件&#xff0c;把Header内容写好。 2、在需要用到的地方&#xff0c;导入…

视频太大发不出去怎么处理,视频太大发不了邮件怎么办

在数字化时代&#xff0c;视频已成为我们分享生活、传递信息的重要方式。然而&#xff0c;当遇到视频文件过大&#xff0c;无法发送或分享时&#xff0c;你是否感到困扰&#xff1f;别担心&#xff0c;本文将为你揭秘轻松解决视频太大发不了的问题。 电脑频编辑器可以用于简单的…

致力于打造一个操作最简单、功能最全面、创意最丰富的聊天记录管理工具

管理您的聊天数据 下载&#xff1a;https://download.csdn.net/download/mo3408/89497474 提供数据获取、导出、分析全栈式解决方案 获取信息 一键式操作&#xff0c;数据信手拈来 导出聊天记录 批量导出、自定义时间、消息类型任意选、Word、Excel、HTML、TXT想要哪个勾哪个…

Study of Stylized Facts in Stock Market Data 股市数据中的程式化事实研究

目录 摘要介绍数据说明3. Stylized Empirical Facts3.1 Univariate Distributional Stylized Empirical Facts3.1.1 Gain Loss Assymetry 损益不对称3.1.2 Leverage Effect 杠杆效应3.1.3 Aggregational Gaussinity 聚合高斯性3.1.4重尾(Heavy Tail)3.1.5 Decay of Distributio…

注意!高考志愿填报的两个优先原则,千万不要错过!

高考已经告一段落&#xff0c;接下来几天各省会陆续公布分数&#xff0c;然后就到了填报志愿的环节。高考志愿填报是一项影响深远的综合性决策&#xff0c;决定着每个考生的未来发展 。下面我谈谈我对高考填报的理解。我总结为&#xff1a;两个优先、三个因素。 一、两个优先 …

数据科学入门-初学者指南

数据科学入门-初学者指南 过去二十年&#xff0c;您并非与世隔绝&#xff0c;所以您可能认为自己或多或少知道数据科学是什么。您可能希望简要了解一下数据科学的涵义&#xff0c;了解开始学习数据科学和找工作所需的条件。 以下是本文将为您提供的重点内容&#xff1a; 数据科…

idea 内存参数修改不生效问题解决 VM参数设置不生效解决

很多人配置idea 内存参数&#xff0c;怎么配置都不生效&#xff0c;主要原因是配置文件用的不是你修改的那个。 系统环境变量中的这个才是你真正要修改的配置文件。 找到并修改后保存&#xff0c;重启idea就可生效

AI工具大盘点!打工人必备的几款效率神器!

前言 在这个AI技术大放异彩的时代&#xff0c;找到合适的工具&#xff0c;可以让我们的工作效率翻倍。作为一名AI工具测评博主&#xff0c;我今天要向大家推荐几款我亲自体验并认为非常实用的AI工具。它们不仅能够提升你的工作效率&#xff0c;还能让你在职场上更加得心应手。…

setjmp和longjmp函数使用

这里用最简单直接的描述&#xff1a;这两组函数是用于实现类似vscode全局的标签跳转功能&#xff0c;setjmp负责埋下标签&#xff0c;longjmp负责标签跳转。 #include <stdio.h> #include <stdlib.h> #include <setjmp.h>jmp_buf envbuf1; jmp_buf envbuf2;…

669分,武汉市第四十九中传来喜讯,璞公英「教师增值评价系统」助力学业提升!

星光熠熠&#xff0c;梦想启航 武汉第四十九中学传来喜讯&#xff01; 高三&#xff08;5&#xff09;班物理类考生潘俊安同学 以总分669分的优异成绩脱颖而出&#xff01; 潘俊安同学不仅实现了自我超越&#xff0c; 更为学校的高考历史增添了浓墨重彩的一笔。 三年磨一…

MTK6769芯片性能参数_MT6769规格书_datasheet

联发科MT6769处理器采用了台积电12nm工艺。它具有8核CPU&#xff0c;采用2Cortex A75 2.0GHz 6Cortex A55 1.7GHz的构架。该处理器搭载了Mali-G52 MC2 GPU&#xff0c;运行速度高达820MHz&#xff0c;能够提供出色的图形处理性能。此外&#xff0c;MT6769还提供高达8GB的快速L…

Prometheus 监控Kubelet的运行状态

kubelet通过/metrics暴露自身的指标数据。kubelet有两个端口都提供了这个url&#xff0c;一个是安全端口&#xff08;10250&#xff09;&#xff0c;一个是非安全端口&#xff08;10255&#xff0c;kubeadm安装的集群该端口是关闭的&#xff09;。安全端口使用https协议&#x…

盘点几款国产AI高效神器!打工人赶紧码住

在这个AI技术飞速发展的时代&#xff0c;国产AI工具正成为提升工作效率的得力助手。作为AI工具测评博主&#xff0c;米兔有幸体验了多款国产AI工具&#xff0c;今天要向大家介绍几款超级好用的AI工具。这些工具不仅功能强大&#xff0c;而且操作简便&#xff0c;是职场人士不可…

# windows 安装 mysql 显示 no packages found 解决方法

windows 安装 mysql 显示 no packages found 解决方法 一、问题描述&#xff1a; 安装 mysql 时&#xff0c;出现 no packages found 不能进行下一步安装了&#xff0c; 如下图&#xff1a; 二、解决方法&#xff1a; 1、路径问题&#xff0c;系统不能识别你的安装包路径&…

航空数据管控系统-②项目分析与设计:任务1:需求分析-项目场景引入

任务描述 知识点&#xff1a;需求分析 重 点&#xff1a;原型设计工具&#xff0c;用例图&#xff0c;流程图绘制工具 难 点&#xff1a;功能点的梳理 内 容&#xff1a;完成本次实训项目的需求分析 先共同讨论处本项目的主要功能模块&#xff0c;并确定每个模块的负责…

ClickHouse概述

ClickHouse概述 文章目录 ClickHouse概述ClickHouse是什么ClickHouse快的理由什么是OLAPClickHouse的特点列式存储DBMS 的功能多样化引擎高吞吐写入能力数据分区与线程级并行 ClickHouse的应用合适场景不适合场景 ClickHouse是什么 ClickHouse 是俄罗斯的 Yandex 于 2016 年开…

Axure教程:App侧边抽屉菜单交互制作

今天给大家示范一下抽屉菜单在Axure中的做法。在抽屉式菜单中&#xff0c;要实现两个交互效果&#xff0c;分别是&#xff1a; 交互一 抽屉菜单中1、2级菜单项的伸缩效果 实现逻辑&#xff1a;设置动态面板的切换状态及“推动/拉动原件”实现 交互二 菜单项的选中状态切换 …

20240705 每日AI必读资讯

&#x1f4da;Retool 刚刚发布了最新2024上半年《人工智能现状报告》 - 收集了约750名技术人员的意见 - 包括开发者、数据团队和各行业的领导者&#xff0c;了解如何利用人工智能产生真正的影响。 &#x1f517; 2024上半年《人工智能现状报告》Retool刚刚发布了最新-CSDN b…