坚持刷题|二叉树的前、中、后序遍历(递归迭代)

news2024/9/25 9:38:48

文章目录

  • 题目
  • 思考
  • 递归实现
  • 迭代实现
    • 前序遍历
    • 后序遍历
    • 中序遍历
  • 在前、中、后序的迭代遍历中,为什么都采用栈来模拟递归,而非队列?

Hello,大家好,我是阿月。坚持刷题,老年痴呆追不上我,今天刷:二叉树的前中后序遍历

题目

分别实现二叉树的前中后序遍历。
在这里插入图片描述

思考

  • 前、中、后序遍历分别指的是根节点在查询中的位置
    • 前序遍历:–>左–>右
    • 中序遍历:左–>–>右
    • 后续遍历:左–>–>中
  • 二叉树前、中、后序的遍历通常是通过递归或迭代的方式实现对二叉树节点的访问顺序。这些遍历方式是树结构数据存储与检索的基本操作,对于解决与树相关的问题非常重要。

递归实现

递归方式解决二叉树问题最重要的是先知道当前根节点需要做什么,然后再根据函数定义进行递归调用子节点,如何提炼出叶子结点要做的事情也正是二叉树的难点所在。

// 递归法
class Solution {
	// 前序遍历
    public  void preorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
        result.add(root.val);
        inorder(root.left, result);
        inorder(root.right, result);
    }
    // 中序遍历
    public  void inorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
        inorder(root.left, result);
        result.add(root.val);
        inorder(root.right, result);
    }
    // 后序遍历
    public  void postorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
        inorder(root.left, result);
        inorder(root.right, result);
        result.add(root.val);
    }
	public List<Integer> traversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        inorder(root, result);
        return result;
    }
}

代码使用Java语言通过递归方法实现二叉树的前序、中序和后序遍历:
前序遍历(Preorder Traversal)

  • 访问根节点。
  • 递归地对左子树进行前序遍历。
  • 递归地对右子树进行前序遍历。

中序遍历(Inorder Traversal)

  • 递归地对左子树进行中序遍历。
  • 访问根节点。
  • 递归地对右子树进行中序遍历。

后序遍历(Postorder Traversal)

  • 递归地对左子树进行后序遍历。
  • 递归地对右子树进行后序遍历。
  • 访问根节点。

可以发现在递归实现不同顺序的遍历时,调整的只是对跟节点的访问顺序。

迭代实现

前序遍历

后序遍历的顺序是根节点、左子树、右子树。

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class PreorderTraversalIterative {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }

        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);

        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            result.add(node.val);  // 将节点值添加到结果列表中

            // 注意:因为栈是先进后出的结构,所以先将右子节点压栈,再将左子节点压栈
            if (node.right != null) {
                stack.push(node.right);
            }
            if (node.left != null) {
                stack.push(node.left);
            }
        }
        
        return result;
    }

    public static void main(String[] args) {
        // 创建一个二叉树
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);

        PreorderTraversalIterative traversal = new PreorderTraversalIterative();
        List<Integer> result = traversal.preorderTraversal(root);
        System.out.println(result);
    }
}
  • 在迭代的方式来实现前序遍历时,需要通过栈来实现递归的过程。
  • 在迭代的过程中,需要合理地利用栈来存储待访问的节点,以及维护遍历顺序的正确性。
  • 在前序遍历中,需要先访问根节点,然后是左子树,最后是右子树。因此,在将节点压入栈时,需要保证右子树先于左子树进栈,以确保弹栈顺序的正确性。

后序遍历

后序遍历的顺序是左子树、右子树、根节点。
可通过对前序遍历的代码稍加改动来实现后序遍历:

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class PostorderTraversalIterative {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }

        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);

        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            // 在结果列表的最前面插入节点值,以保证后序遍历的顺序
            result.add(0, node.val);

            // 注意:因为栈是先进后出的结构,所以先将左子节点压栈,再将右子节点压栈
            if (node.left != null) {
                stack.push(node.left);
            }
            if (node.right != null) {
                stack.push(node.right);
            }
        }

        return result;
    }

    public static void main(String[] args) {
        // 创建一个二叉树
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);

        PostorderTraversalIterative traversal = new PostorderTraversalIterative();
        List<Integer> result = traversal.postorderTraversal(root);
        System.out.println(result);
    }
}

在后序遍历中,需要在结果列表的最前面插入节点的值,这样可以保证后序遍历的顺序。在迭代过程中,先将左子节点压入栈,再将右子节点压入栈,然后在结果列表的最前面插入根节点的值。

中序遍历

因为中序遍历需要先访问左子树,然后访问根节点,最后访问右子树,所以中序遍历的迭代实现稍微有些不同。过程中需要在沿着左子树一直走到底的时候才能访问根节点,因此在使用迭代实现中序遍历时,需要确保在访问完左子树之后才访问根节点。

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class InorderTraversalIterative {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }

        Stack<TreeNode> stack = new Stack<>();
        TreeNode current = root;

        while (current != null || !stack.isEmpty()) {
            // 先将左子树的所有节点压栈
            while (current != null) {
                stack.push(current);
                current = current.left;
            }

            // 当前节点为 null 时,表示已经到达左子树的最左边了,可以弹出节点并访问
            current = stack.pop();
            result.add(current.val);

            // 继续遍历右子树
            current = current.right;
        }

        return result;
    }

    public static void main(String[] args) {
        // 创建一个二叉树
        TreeNode root = new TreeNode(1);
        root.right = new TreeNode(2);
        root.right.left = new TreeNode(3);

        InorderTraversalIterative traversal = new InorderTraversalIterative();
        List<Integer> result = traversal.inorderTraversal(root);
        System.out.println(result);
    }
}

在这个实现中,我们利用了一个栈来模拟递归的过程。我们从根节点开始,一直向左走到最底层的左子树,将沿途遇到的节点压入栈中。然后开始弹出栈顶的节点并访问它,然后转向其右子树,重复这个过程,直到栈为空并且当前节点也为空时,遍历结束。

在前、中、后序的迭代遍历中,为什么都采用栈来模拟递归,而非队列?

  • 栈具有先进后出(Last In First Out,LIFO)的特性。
    • 深度优先遍历的特性: 前、中、后序遍历都是深度优先遍历,意味着在遍历时首先访问一个节点,然后深入到其子节点。栈非常适合用于深度优先遍历,因为栈可以保存当前节点,随时回溯到父节点。递归实际上就是在系统调用栈中保存了当前状态,栈结构天然符合深度优先的遍历顺序。
    • 维护遍历顺序: 在前序遍历中,根节点首先被访问,然后是左子树,最后是右子树。在中序遍历中,左子树首先被访问,然后是根节点,最后是右子树。在后序遍历中,左子树和右子树都在根节点之前被访问。这些特性使得使用栈可以方便地维护遍历的顺序。
  • 队列具有先进先出(First In First Out,FIFO)的特性。
    • 递归调用的模拟: 在使用栈模拟深度遍历的递归时,每次都将当前节点压入栈中,然后按照特定的顺序访问其子节点,而队列的先进先出(First In First Out,FIFO)特性不太适合模拟此类递归。
    • 如果使用队列来实现迭代的遍历,会导致按照广度优先的顺序遍历树的节点,而前、中、后序的遍历都是深度优先的遍历。

总之,栈在模拟深度优先遍历和维护遍历顺序时更加自然和方便,因此在前、中、后序遍历的迭代实现中常常采用栈来模拟递归。队列则更适合广度优先遍历,比如 坚持刷题 | 二叉树的层序遍历等。

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

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

相关文章

[SWPUCTF 2021 新生赛]ez_unserialize

根据下面的user_agent和Disallow可以判断这个是在robots.txt 我们看的出来这是一个反序列化需要我们adminadmin passwdctf construct 构造方法&#xff0c;当一个对象被创建时调用此方法&#xff0c;不过unserialize()时却不会被调用 destruct 析构方法&#xff0c;PHP将在对象…

分别用JavaScript,Java,PHP,C++实现桶排序的算法(附带源码)

桶排序是计数排序的升级版。它利用了函数的映射关系&#xff0c;高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效&#xff0c;我们需要做到这两点&#xff1a; 在额外空间充足的情况下&#xff0c;尽量增大桶的数量使用的映射函数能够将输入的 N 个数据均匀的分…

MySQL数据库②_库和表的操作_增删查改_备份恢复

目录 1. 创建数据库 2. 字符集和校验规则 2.1 默认的字符集和校验规则 2.2 支持的字符集校验规则 2.3 校验规则对数据库的影响 3. 库的查看&#xff0c;修改&#xff0c;删除 3.1 查看数据库 3.2 修改数据库 3.3 删除数据库 4. 库的备份和恢复 4.1 备份数据库 4.2 …

在虚拟机上搭建CentOS环境并配置静态IP

在虚拟机上搭建CentOS环境并配置静态IP 在进行Linux系统的学习和实践时&#xff0c;搭建一个本地的CentOS环境是一个非常好的方式。本文将介绍如何使用虚拟机&#xff08;VM&#xff09;搭建CentOS环境&#xff0c;并配置静态IP&#xff0c;以便更好地进行网络管理和测试。 步…

Java开发工具 IntelliJ IDEA 2023中文

IntelliJ IDEA 2023是一款强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;适用于多种编程语言&#xff0c;包括Java、Python、Kotlin等。它提供了许多特色功能&#xff0c;以提高开发效率和代码质量。 Java开发工具 IntelliJ IDEA 2023中文 以下是一些IntelliJ ID…

机器学习复习(4)——CNN算法

目录 数据增强方法 CNN图像分类数据集构建 导入数据集 定义trainer 超参数设置 数据增强 构建CNN网络 开始训练 模型测试 数据增强方法 # 一般情况下&#xff0c;我们不会在验证集和测试集上做数据扩增 # 我们只需要将图片裁剪成同样的大小并装换成Tensor就行 test_t…

跨平台开发:浅析uni-app及其他主流APP开发方式

随着智能手机的普及&#xff0c;移动应用程序&#xff08;APP&#xff09;的需求不断增长。开发一款优秀的APP&#xff0c;不仅需要考虑功能和用户体验&#xff0c;还需要选择一种适合的开发方式。随着技术的发展&#xff0c;目前有多种主流的APP开发方式可供选择&#xff0c;其…

【计网·湖科大·思科】实验七 路由信息协议RIP、开放最短路径优先协议OSPF、边界网关协议BGP

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

nginx初学者指南

一、启动、停止和重新加载配置 前提&#xff1a;先要启动nginx 在Windows上启动nginx的步骤如下&#xff1a; 1. 下载并安装nginx。可以从nginx官网下载适合自己操作系统的版本&#xff0c;一般是zip压缩包&#xff0c;解压到指定目录中。 2. 进入nginx的安装目录&#xff…

简单几步,借助Aapose.Cells将 Excel 工作表拆分为文件

近年来&#xff0c;Excel 文件已成为无数企业数据管理的支柱。然而&#xff0c;管理大型 Excel 文件可能是一项艰巨的任务&#xff0c;尤其是在高效共享和处理数据时。为了应对这一挑战&#xff0c;大型 Excel 工作簿被拆分为较小的工作簿以增强电子表格管理。Aspose提供了这样…

electron项目在内网环境的linux环境下进行打包

Linux需要的文件: electron-v13.0.0-linux-x64.zip appimage-12.0.1.7z snap-template-electron-4.0-1-amd64.tar.7z 下载慢或者下载失败的情况可以手动下载以上electron文件复制到指定文件夹下&#xff1a; 1.electron-v13.0.0-linux-x64.zip 复制到~/.cache/electron/目录下…

Blender使用Rigify和Game Rig Tool基础

做动画需要的几个简要步骤&#xff1a; 1.建模 2.绑定骨骼 3.绘制权重 4.动画 有一个免费的插件可以处理好给引擎用&#xff1a;Game Rig Tool 3.6和4.0版本的 百度网盘 提取码&#xff1a;vju8 1.Rigify是干嘛用的&#xff1f; 》 绑定骨骼 2.Game Rig Tool干嘛用的&#xf…

LVGL部件8

一.按钮矩阵部件 1.知识概览 2.函数接口 1.lv_btnmatrix_set_btn_ctrl 在 LVGL&#xff08;LittlevGL&#xff09;中&#xff0c;lv_btnmatrix_set_btn_ctrl() 函数用于设置按钮矩阵&#xff08;Button Matrix&#xff09;中单个按钮的控制选项。该函数可以用来定制按钮矩阵中…

寒假作业2月3号

第二章 引用内联重载 一&#xff0e;选择题 1、适宜采用inline定义函数情况是&#xff08;C&#xff09; A. 函数体含有循环语句 B. 函数体含有递归语句 C. 函数代码少、频繁调用 D. 函数代码多、不常调用 2、假定一个函数为A(int i4, int j0) {;}, 则执行“A (1);”语句…

【蓝桥杯】环形链表的约瑟夫问题

目录 题目描述&#xff1a; 输入描述&#xff1a; 输出描述&#xff1a; 示例1 解法一&#xff08;C&#xff09;&#xff1a; 解法二&#xff08;Cpp&#xff09;&#xff1a; 正文开始&#xff1a; 题目描述&#xff1a; 据说著名犹太历史学家 Josephus 有过以下故事&a…

UE4 C++ 枚举类型

先在UCLASS()前写入&#xff1a; //定义枚举变量&#xff1a;方法一 UENUM(BlueprintType) //BlueprintType&#xff1a;在蓝图中可显示、创建该枚举变量 namespace MyEnumType //namespace&#xff1a;命名空间&#xff0c;支持同样的变量命令、便于访问//MyEnumType&#xf…

如何保证MySQL和Redis中的数据一致性?

文章目录 前言一、缓存案例1.1 缓存常见用法1.2 缓存不一致产生的原因 二、解决方案2.1 先删除缓存&#xff0c;再更新数据库2.2 先更新数据库&#xff0c;删除缓存2.3 只更新缓存&#xff0c;由缓存自己同步更新数据库2.4 只更新缓存&#xff0c;由缓存自己异步更新数据库2.5 …

【MybatisPlus篇】查询条件设置(范围匹配 | 模糊匹配 | 空判定 | 包含性判定 | 分组 | 排序)

文章目录 &#x1f384;环境准备⭐导入依赖⭐写入User类⭐配置启动类⭐创建UserDao 的 MyBatis Mapper 接口&#xff0c;用于定义数据库访问操作⭐创建配置文件&#x1f6f8;创建测试类MpATest.java &#x1f354;范围查询⭐eq⭐between⭐gt &#x1f354;模糊匹配⭐like &…

力扣之2629.复合函数(reduceRight )

/*** param {Function[]} functions* return {Function}*/ var compose function(functions) {return function(x) {return functions.reduceRight((result, func) > func(result), x);} };/*** const fn compose([x > x 1, x > 2 * x])* fn(4) // 9*/ 说明&#x…

docker 容器指定主机网段

docker 容器指定主机网段。 直接连接到物理网络&#xff1a;使用macvlan技术可以让Docker容器直接连接到物理网络&#xff0c;而不需要通过NAT或端口映射的方式来访问它们。可以提高网络性能和稳定性&#xff0c;同时也可以使容器更加透明和易于管理。 1、查询网卡的名称&…