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

文章目录

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

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.hqwc.cn/news/450407.html

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

相关文章

030 可变参数

可变参数定义 public static void main(String[] args) {// 多参数方式传递System.out.println(max(1,3,5,3,6,1,2));// 数组方式传递System.out.println(max(new int[]{1,3,5,3,6,1,2})); }static int max(int... nums){int max Integer.MIN_VALUE;for (int num : nums) {if(…

redis布隆过滤器(Bloom)详细使用教程

文章目录 布隆过滤器1. 原理2. 结构和操作3. 特点和应用场景4. 缺点和注意事项 应用-redis插件布隆过滤器使用详细过程安装以及配置springboot项目使用redis布隆过滤器下面是布隆过滤器的一些基础命令 扩展 布隆过滤器 Bloom 过滤器是一种概率型数据结构&#xff0c;用于快速判…

echarts step line

https://ppchart.com/#/ <template><div class"c-box" ref"jsEchart"></div> </template><script> import * as $echarts from echarts // 事件处理函数 export default {props: {// 需要传递的数据data: {type: Array,defa…

SSL 证书如何工作?

SSL 的原理是确保用户和网站之间或两个系统之间传输的任何数据始终无法被读取。它使用加密算法对传输中的数据进行加密&#xff0c;从而防止黑客读取通过连接发送的数据。该数据包括潜在的敏感信息&#xff0c;例如姓名、地址、信用卡号或其他财务详细信息。 该过程如下所示&am…

ElementUI Data:Table 表格

ElementUI安装与使用指南 Table 表格 点击下载learnelementuispringboot项目源码 效果图 el-table.vue&#xff08;Table表格&#xff09;页面效果图 项目里el-table.vue代码 <script> export default {name: el_table,data() {return {tableData: [{dat…

向日葵案例解析:无外网接入,医疗设备如何进行远程售后运维

随着医学科学以及生物工程技术的高速发展&#xff0c;医院对于高端医疗设备如MR、CT、B超等高科技成像设备和放射治疗设备的需求激增。医学影像检查作为一种重要的手段&#xff0c;在许多疾病确诊过程中发挥着至关重要的作用。检查结果正确与否&#xff0c;直接影响临床医生对疾…

Emmet语法

一&#xff0c;emmet语法快速生成HTML标签 二&#xff0c;emmet语法快速生成CSS样式 简写 三&#xff0c;快速格式化代码 右键选择格式化文档。

Docker进阶篇-Docker网络

一、描述 1、docker不启动&#xff0c;默认网络情况 查看网卡情况使用&#xff0c;ifconfig或者ip addr ens33&#xff1a;本机网卡 lo&#xff1a;本机回环网络网卡 virbr0:在CentoS 7的安装过程中如果有选择相关虚拟化的的服务安装系统后&#xff0c;启动网卡时会发现 …

异步编程Completablefuture使用详解----进阶篇

JDK版本&#xff1a;jdk17 IDEA版本&#xff1a;IntelliJ IDEA 2022.1.3 文章目录 前言一、异步任务的交互1.1 applyToEither1.2 acceptEither1.3 runAfterEither 二、get() 和 join() 区别三、ParallelStream VS CompletableFuture3.1 使用串行流执行并统计总耗时3.2 使用并行…

什么是DDOS流量攻击,DDoS防护安全方案

随着互联网的发展普及&#xff0c;云计算成新趋势&#xff0c;人们对生活方式逐渐发生改变的同时&#xff0c;随之而来的网络安全威胁也日益严重&#xff01; 目前在网络安全方面&#xff0c;网络攻击是最主要的威胁之一&#xff0c;其中DDoS攻击是目前最为常见的网络攻击手段…

利用OpenCV实现物流与生产线自动化的革命性突破

背景介绍&#xff1a; 在当今高度自动化的时代&#xff0c;物流和生产线上的每一个环节都关乎企业的核心竞争力。传统的生产方式往往依赖于人工检测和操作&#xff0c;这不仅效率低下&#xff0c;而且容易出错。为了解决这一问题&#xff0c;越来越多的企业开始寻求利用计算机视…

京东微前端框架MicroApp简介

一、MicroApp 1.1 MicroApp简介 MicroApp是由京东前端团队推出的一款微前端框架,它从组件化的思维,基于类WebComponent进行微前端的渲染,旨在降低上手难度、提升工作效率。MicroApp无关技术栈,也不和业务绑定,可以用于任何前端框架。 官网链接:https://micro-zoe.gith…