二叉树的遍历与构造

news2025/7/19 21:04:36

好想回家,我想回家跟馒头酱玩,想老爸老妈。如果上天再给我一次选择的机会,我会选择当一只小动物,或者当棵大树也好,或者我希望自己不要有那么多多余的情绪,不要太被别人影响,开心点,想睡就睡,想玩就玩,不要为难自己。老爸每次都和我说累了就回家,但越是这样我就越希望自己变得更强大一点。希望明天是个好天气。

目录

一、遍历

1 前序遍历

(1)递归

(2)非递归(先右后左哈)

2 中序遍历

(1)递归

(2)非递归

3 后序遍历

(1)递归

(2)非递归(中右左,反转列表后变成左右中)

4 层序遍历

(1)bfs(借助队列)

(2)dfs

二、构造

1 构建最大二叉树

2 由 前 中 构造

3 由 前 后 构造

4 由 中 后 构造


一、遍历

1 前序遍历

(1)递归

   private static void preorderHelper(TreeNode node, List<Integer> result) {
        if (node == null) return;
        result.add(node.val);
        preorderHelper(node.left, result);
        preorderHelper(node.right, result);
    }

(2)非递归(先右后左哈)

// 前序遍历 - 非递归
    public static List<Integer> preorderTraversalIterative(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;
    }

2 中序遍历

(1)递归

 private static void inorderHelper(TreeNode node, List<Integer> result) {
        if (node == null) return;
        inorderHelper(node.left, result);
        result.add(node.val);
        inorderHelper(node.right, result);
    }

(2)非递归

核心思路是先将当前节点及其所有左子节点依次入栈,直到左子节点为空,接着从栈中弹出节点进行访问,然后将当前节点更新为弹出节点的右子节点,继续重复上述操作。

// 中序遍历 - 非递归
    public static List<Integer> inorderTraversalIterative(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode current = root;
        while (current != null || !stack.isEmpty()) {
            while (current != null) {
                stack.push(current);
                current = current.left;
            }
            current = stack.pop();
            result.add(current.val);
            current = current.right;
        }
        return result;
    }

3 后序遍历

(1)递归

private static void postorderHelper(TreeNode node, List<Integer> result) {
        if (node == null) return;
        postorderHelper(node.left, result);
        postorderHelper(node.right, result);
        result.add(node.val);
    }

(2)非递归(中右左,反转列表后变成左右中)

 // 后序遍历 - 非递归
    public static List<Integer> postorderTraversalIterative(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.left != null) stack.add(node.left);
            if (node.right != null) stack.add(node.right);
        }
        Collections.reverse(result);
        return result;
    }

4 层序遍历

(1)bfs(借助队列)

 // 层序遍历 - BFS(借助队列)
    public static List<List<Integer>> levelOrderTraversalBFS(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) return result;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int levelSize = queue.size();
            List<Integer> level = new ArrayList<>();
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            result.add(level);
        }
        return result;
    }

(2)dfs

 // 层序遍历 - DFS
    public static List<List<Integer>> levelOrderTraversalDFS(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        dfs(root, 0, result);
        return result;
    }

    private static void dfs(TreeNode node, int level, List<List<Integer>> result) {
        if (node == null) return;
        if (level >= result.size()) {
            result.add(new ArrayList<>());
        }
        result.get(level).add(node.val);
        dfs(node.left, level + 1, result);
        dfs(node.right, level + 1, result);
    }

二、构造

1 构建最大二叉树

给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:

创建一个根节点,其值为 nums 中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 最大二叉树

输入:nums = [3,2,1,6,0,5]
输出:[6,3,5,null,2,0,null,null,1]
解释:递归调用如下所示:

[3,2,1,6,0,5] 中的最大值是 6 ,左边部分是 [3,2,1] ,右边部分是 [0,5] 。
[3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。
空数组,无子节点。
[2,1] 中的最大值是 2 ,左边部分是 [] ,右边部分是 [1] 。
空数组,无子节点。
只有一个元素,所以子节点是一个值为 1 的节点。
[0,5] 中的最大值是 5 ,左边部分是 [0] ,右边部分是 [] 。
只有一个元素,所以子节点是一个值为 0 的节点。
空数组,无子节点。


解法&思路

首先要解决的是找到每次递归传入数组的最大值做为根节点
该最大值下标左边的元素递归构造为左子树,右边的元素递归构造为右子树

// 定义二叉树节点类
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

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

class Solution {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        return build(nums, 0, nums.length);
    }

    private TreeNode build(int[] nums, int start, int end) {
        // 如果数组为空,返回 null
        if (start == end) {
            return null;
        }

        // 找到最大值和最大值的索引
        int maxIndex = start;
        for (int i = start + 1; i < end; i++) {
            if (nums[i] > nums[maxIndex]) {
                maxIndex = i;
            }
        }

        // 用最大值创建根节点
        TreeNode rootNode = new TreeNode(nums[maxIndex]);

        // 最大值左侧为左子树
        rootNode.left = build(nums, start, maxIndex);

        // 最大值右侧为右子树
        rootNode.right = build(nums, maxIndex + 1, end);

        return rootNode;
    }
}

2 由 前 中 构造

// 定义二叉树节点类
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

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

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return build(preorder, inorder, 0, 0, inorder.length - 1);
    }

    private TreeNode build(int[] preorder, int[] inorder, int preStart, int inStart, int inEnd) {
        // 如果中序遍历的起始下标大于结束下标,说明当前子树为空
        if (inStart > inEnd) {
            return null;
        }

        // 从前序遍历中找到根节点
        int rootVal = preorder[preStart];
        TreeNode rootNode = new TreeNode(rootVal);

        // 在中序遍历中找到根节点的位置
        int rootIndex = -1;
        for (int i = inStart; i <= inEnd; i++) {
            if (inorder[i] == rootVal) {
                rootIndex = i;
                break;
            }
        }

        // 计算左子树的大小
        int leftSize = rootIndex - inStart;

        // 递归构造左子树
        // 左子树的前序遍历范围:preStart + 1 到 preStart + leftSize
        // 左子树的中序遍历范围:inStart 到 rootIndex - 1
        rootNode.left = build(preorder, inorder, preStart + 1, inStart, rootIndex - 1);

        // 递归构造右子树
        // 右子树的前序遍历范围:preStart + leftSize + 1 到 preStart + leftSize + 右子树大小
        // 右子树的中序遍历范围:rootIndex + 1 到 inEnd
        rootNode.right = build(preorder, inorder, preStart + leftSize + 1, rootIndex + 1, inEnd);

        return rootNode;
    }
}

至于为什么只需要中序遍历的终点,而不需要前序遍历的终点?因为在我们的思路中其实可以发现只需要前序遍历的起点确认根节点的值,并不需要终点值。我们可以随便选择一个遍历的终点值用来确认边界值,即这部分代码。

if(inStart > inEnd){
   return null
}

3 由 前 后 构造

// 定义二叉树节点类
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

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

class Solution {
    public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
        return build(preorder, 0, preorder.length - 1, postorder, 0, postorder.length - 1);
    }

    private TreeNode build(int[] preorder, int preStart, int preEnd, int[] postorder, int postStart, int postEnd) {
        // 如果前序遍历的起始下标大于结束下标,说明当前子树为空
        if (preStart > preEnd) {
            return null;
        }

        // 当前子树只有一个节点时,直接返回该节点
        if (preStart == preEnd) {
            return new TreeNode(preorder[preStart]);
        }

        // 找到根节点
        int rootVal = preorder[preStart];
        TreeNode root = new TreeNode(rootVal);

        // 找到左子树起点在后序遍历中的位置
        int leftStartIndex = -1;
        for (int i = postStart; i <= postEnd; i++) {
            if (postorder[i] == preorder[preStart + 1]) {
                leftStartIndex = i;
                break;
            }
        }

        // 计算左子树的长度
        int size = leftStartIndex - postStart + 1;

        // 递归构造左子树
        // 左子树的前序范围:preStart + 1 到 preStart + size
        // 左子树的后序范围:postStart 到 leftStartIndex
        root.left = build(preorder, preStart + 1, preStart + size, postorder, postStart, leftStartIndex);

        // 递归构造右子树
        // 右子树的前序范围:preStart + size + 1 到 preEnd
        // 右子树的后序范围:leftStartIndex + 1 到 postEnd - 1
        root.right = build(preorder, preStart + size + 1, preEnd, postorder, leftStartIndex + 1, postEnd - 1);

        return root;
    }
}

4 由 中 后 构造

// 定义二叉树节点类
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

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

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        return build(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1);
    }

    private TreeNode build(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) {
        // 如果中序遍历的起始下标大于结束下标,说明当前子树为空
        if (inStart > inEnd) {
            return null;
        }

        // 后序遍历的最后一个节点是根节点
        int rootVal = postorder[postEnd];
        TreeNode rootNode = new TreeNode(rootVal);

        // 找到根节点在中序遍历中的位置
        int rootIndex = -1;
        for (int i = inStart; i <= inEnd; i++) {
            if (inorder[i] == rootVal) {
                rootIndex = i;
                break;
            }
        }

        // 计算左子树的大小
        int leftSize = rootIndex - inStart;

        // 递归构造左子树
        // 左子树的中序范围:inStart 到 rootIndex - 1
        // 左子树的后序范围:postStart 到 postStart + leftSize - 1
        rootNode.left = build(inorder, inStart, rootIndex - 1, postorder, postStart, postStart + leftSize - 1);

        // 递归构造右子树
        // 右子树的中序范围:rootIndex + 1 到 inEnd
        // 右子树的后序范围:postStart + leftSize 到 postEnd - 1
        rootNode.right = build(inorder, rootIndex + 1, inEnd, postorder, postStart + leftSize, postEnd - 1);

        return rootNode;
    }
}

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

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

相关文章

MYSQL服务的使用流程

MYSQL是一个单进程多线程&#xff0c;支持多用户&#xff0c;基于客户机/服务器的关系数据库管理系统。与其他数据库管理系统相比&#xff0c;MYSQL具有体积小&#xff0c;易于安装&#xff0c;运行速度快&#xff0c;功能齐全&#xff0c;成本低廉以及开源等特点。MYSQL可运行…

【java】使用iText实现pdf文件增加水印功能

maven依赖 <dependencies><dependency><groupId>com.itextpdf</groupId><artifactId>itext7-core</artifactId><version>7.2.5</version><type>pom</type></dependency> </dependencies>实现代码 前…

socket套接字-TCP

上一篇&#xff1a;socket套接字-UDP&#xff08;下&#xff09;https://blog.csdn.net/Small_entreprene/article/details/147569071?fromshareblogdetail&sharetypeblogdetail&sharerId147569071&sharereferPC&sharesourceSmall_entreprene&sharefromfr…

MiM: Mask in Mask Self-SupervisedPre-Training for 3D Medical Image Analysis

Abstract Vision Transformer在3D医学图像分析的自监督学习&#xff08;Self-Supervised Learning&#xff0c;SSL&#xff09;中展现了卓越的性能。掩码自编码器&#xff08;Masked Auto-Encoder&#xff0c;MAE&#xff09;用于特征预训练&#xff0c;可以进一步释放ViT在各…

【STM32 学习笔记】I2C通信协议

注&#xff1a;通信协议的设计背景 3:00~10:13 I2C 通讯协议(Inter&#xff0d;Integrated Circuit)是由Phiilps公司开发的&#xff0c;由于它引脚少&#xff0c;硬件实现简单&#xff0c;可扩展性强&#xff0c; 不需要USART、CAN等通讯协议的外部收发设备&#xff0c;现在被广…

深入理解卷积神经网络的输入层:数据的起点与预处理核心

内容摘要 本文围绕卷积神经网络输入层展开&#xff0c;详细介绍其在网络中的重要作用&#xff0c;包括接收不同领域数据的形式及传递数据的过程。深入解读数据预处理的关键操作&#xff0c;如去均值、归一化和PCA/白化。助力读者透彻理解输入层&#xff0c;为构建高效卷积神经…

redis bitmap数据类型调研

一、bitmap是什么&#xff1f; redis原文&#xff1a; Bitmaps are not an actual data type, but a set of bit-oriented operations defined on the String type . This means that bitmaps can be used with string commands, and most importantly with SET and GET. 翻…

LabVIEW 2019 与 NI VISA 20.0 安装及报错处理

在使用 Windows 11 操作系统的电脑上&#xff0c;同时安装了 LabVIEW 2019 32 位和 64 位版本的软件。此前安装的 NI VISA 2024 Q1 版&#xff0c;该版本与 LabVIEW 2019 32 位和 64 位不兼容&#xff0c;之后重新安装了 NI VISA 20.0。从说明书来看&#xff0c;NI VISA 20.0 …

探索 JWT(JSON Web Token):原理、结构与实践应用对比

目录 前言1. 什么是 JWT&#xff1f;2. JWT 的组成结构详解2.1 Header&#xff08;头部&#xff09;2.2 Payload&#xff08;负载&#xff09;2.3 Signature&#xff08;签名&#xff09; 3. JWT 的实际作用3.1 身份认证3.2 信息传递与授权 4. JWT 与 Cookie、API Key 的比较4.…

[docker基础一]docker简介

目录 一 消除恐惧 1) 什么是虚拟化&#xff0c;容器化 2)案例 3)为什么需要虚拟化&#xff0c;容器化 二 虚拟化实现方式 1)应用程序执行环境分层 2)虚拟化常见类别 3)常见虚拟化实现 一&#xff09;主机虚拟化(虚拟机)实现 二&#xff09;容器虚拟化实现 一 消除恐…

Texify - 数学公式OCR转换工具

文章目录 一、项目概览相关资源核心特性 二、安装指南三、使用示例1、命令行转换2、Python API调用3、交互式应用 四、性能基准运行你自己的基准测试 五、局限性 一、项目概览 Texify 是一个OCR模型&#xff0c;可将包含数学公式的图片或PDF转换为Markdown和LaTeX格式&#xf…

RISC-V CLINT、PLIC及芯来ECLIC中断机制分析 —— RISC-V中断机制(一)

在长期的嵌入式开发实践中&#xff0c;对中断机制的理解始终停留在表面层次&#xff0c;特别当开发者长期局限于纯软件抽象层面时&#xff0c;对中断机制的理解极易陷入"知其然而不知其所以然"的困境&#xff0c;这种认知的局限更为明显&#xff1b;随着工作需要不断…

开源与商业:图形化编程工具的博弈与共生

一、开源生态的破局之路&#xff1a;从技术实验到行业标准 在 2025 年全球开发者生态大会上&#xff0c;iVX 凭借 “全栈代码生成 AI 驱动开发” 的技术架构&#xff0c;被行业权威机构评选为 “年度技术创新典范”。作为 2012 年启动的开源项目&#xff0c;iVX 历经 17 年技…

(二)Linux下基本指令 2

【知识预告】 16. date 指令 17. cal 指令 18. find 指令 19. which指令 20. whereis 指令 21. alias 指令 22. grep 指令 23. zip/unzip 指令 24. tar 指令 25. bc 指令 26. uname ‒r 指令 27. 重要的⼏个热键 28. 关机 16 date 指令 指定格式显⽰时间&#xff1a;date %Y-…

无线网络设备中AP和AC是什么?有什么区别?

无线网络设备中AP和AC是什么&#xff1f;有什么区别&#xff1f; 一. 什么是AP&#xff1f;二. 什么是AC&#xff1f;三. AP与AC的关系 前言 肝文不易&#xff0c;点个免费的赞和关注&#xff0c;有错误的地方请指出&#xff0c;看个人主页有惊喜。 作者&#xff1a;神的孩子都…

Web自动化测试入门详解

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、目的 web自动化测试作为软件自动化测试领域中绕不过去的一个“香饽饽”&#xff0c;通常都会作为广大测试从业者的首选学习对象&#xff0c;相较于C/S架…

uniapp+vue3+firstUI时间轴 提现进度样式

展示 说明&#xff1a;“status”: 0, //状态:0待审核,1审核通过,2审核驳回,3提现成功,4提现失败 第一种&#xff1a;5种类型归纳为三种显示样式 <fui-timeaxis background"#fff" :padding"[10rpx,16rpx,0]"><!-- 动态生成步骤节点 --><f…

【日撸 Java 三百行】Day 10(综合任务 1)

目录 Day 10&#xff1a;综合任务 1 一、题目分析 1. 数据结构 2. 相关函数基本知识 二、模块介绍 1. 初始化与成绩矩阵的构建 2. 创建总成绩数组 3. 寻找成绩极值 三、代码与测试 小结 拓展&#xff1a;关于求极值的相关算法 Day 10&#xff1a;综合任务 1 Task&…

macOS 15.4.1 Chrome不能访问本地网络

前言 最近使用macmini m4&#xff0c;自带macOS15系统&#xff0c;对于开发者简直是一言难尽&#xff0c;Chrome浏览器的本地网络有bug&#xff0c;可以访问本机&#xff0c;但是不能访问路由器上的其他机器&#xff0c;路由器提供的页面也不能访问&#xff0c;如下是折腾解决…

【Hive入门】Hive增量数据导入:基于Sqoop的关系型数据库同步方案深度解析

目录 引言 1 增量数据导入概述 1.1 增量同步与全量同步对比 1.2 增量同步技术选型矩阵 2 Sqoop增量导入原理剖析 2.1 Sqoop架构设计 2.2 增量同步核心机制 3 Sqoop增量模式详解 3.1 append模式&#xff08;基于自增ID&#xff09; 3.2 lastmodified模式&#xff08;基…