二叉树理论基础

二叉树种类

满二叉树:每个非叶子节点都有且只有两个子节点。

和完全二叉树:除了最底层外,其他各层都是满的;最底层的节点都集中在左侧。

二叉搜索树:对于任意节点 u,左子树上所有节

点的值都小于 u.val,右子树上所有节点的值都大于 u.val

平衡二叉树任意节点的左右子树高度差 ≤ 1。

二叉树的存储方式

「二叉树可以链式存储,也可以顺序存储。」

那么链式存储方式就用指针, 顺序存储的方式就是用数组。

遍历算法

  1. 深度优先遍历(DFS)

    • 前序(Pre‑Order):根 → 左 → 右

      /*** Definition for a binary tree node.* public 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 List<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new LinkedList<>();preorder(root, res);return res;}void preorder(TreeNode root,List<Integer> list){if(root == null){return;}list.add(root.val);preorder(root.left,list);        preorder(root.right,list);}
      }
    • 中序(In‑Order):左 → 根 → 右

      
      class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new LinkedList<>();inorder(root, res);return res;}void inorder(TreeNode root,List<Integer> list){if(root == null){return;}inorder(root.left,list);list.add(root.val);inorder(root.right,list);}
      }
    • 后序(Post‑Order):左 → 右 → 根

      
      class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new LinkedList<>();inorder(root, res);return res;}void inorder(TreeNode root,List<Integer> list){if(root == null){return;}           inorder(root.left,list);        inorder(root.right,list);list.add(root.val);}
      }
  2. 广度优先遍历(BFS)/层序(Level‑Order)

    • 按层自上而下、同层从左到右依次访问,通常用队列实现。

 二叉树的定义

public 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;}
}

递归写法 

  1. 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。

  2. 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。

  3. 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。

以前序遍历为例:

1.首先,确认参数,因为我们访问的是节点,所以参数要包含节点对象,其次要返回访问的数值,所以也要包含一个list对象保存访问的值

2.每一层递归的终止条件就是遇见空节点,所以判断当前节点是否为空,为空就返回

3. 前序遍历是中左右的顺序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:

迭代解决遍历

前序:因为迭代使用栈,而出栈顺序是先进后出,所以我们在遍历的时候要改变遍历的顺序,先遍历右节点,在遍历左节点,这时候就是左节点先出,符合根左右的习惯

class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> list = new ArrayList<>();if(root == null){return list;}Stack<TreeNode> s = new Stack<>();s.push(root);while(!s.isEmpty()){TreeNode node = s.pop();list.add(node.val);if(node.right != null){s.push(node.right);}if(node.left != null){s.push(node.left);}}return list;
}}

中序:在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null){return result;}Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;while (cur != null || !stack.isEmpty()){if (cur != null){stack.push(cur);cur = cur.left;}else{cur = stack.pop();result.add(cur.val);cur = cur.right;}}return result;}
}

后序:// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果

class Solution {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(node.val);if (node.left != null){stack.push(node.left);}if (node.right != null){stack.push(node.right);}}Collections.reverse(result);return result;}
}

!!层次遍历

思路:需要借助辅助队列来完成统计,即一层一层的入队。

1.首先声明一个外部的辅助队列,用来统计所有的入队出队的值,

2.声明处理一层一层队列的方法,在方法内部创建一个内部队列用来处理每一层中出对入对的值

3.每一层结束的标记是什么,就是内部队列长度为0时,就结束.

public class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> resList = new LinkedList<>();if (root == null) {return resList;}Queue<TreeNode> q = new LinkedList<>();q.add(root);while (!q.isEmpty()) {List<Integer> l = new ArrayList<>();int len = q.size();while (len > 0) {TreeNode node = q.poll();l.add(node.val);if (node.left != null)  q.add(node.left);if (node.right != null) q.add(node.right);len--;}resList.add(l);}return resList;
}}

二叉树自底而上的层次遍历

给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

利用LinkedList的addFirst()方法即可,每一次遍历完都将内部队列得到的值放在前面,即可倒序

!!注意如果将res实际实现为List类型,就无法使用addFirst()方法 

LinkedList<List<Integer>> res = new LinkedList<>();

public List<List<Integer>> levelOrder(TreeNode root) {LinkedList<List<Integer>> res = new LinkedList<>();if (root == null) {return null;}Queue<TreeNode> q = new LinkedList<>();q.add(root);while (!q.isEmpty()) {List<Integer> l = new LinkedList<>();int len = q.size();while (len-- > 0) {TreeNode node = q.poll();l.add(node.val);if (node.left != null)  q.add(node.left);if (node.right != null) q.add(node.right);}res.addFirst(l);}return res;}

二叉树的右视图

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

思路很简单,即每层层序遍历时都将最右边的节点值加入到LIst中,如何判断为最右边的节点?

即每一层处理时,队列长度仅为1时就是最右边的节点

class Solution {public List<Integer> rightSideView(TreeNode root) {List<Integer> res = new ArrayList<>();if (root == null) {return res;}Queue<TreeNode> q = new LinkedList<>();q.add(root);while (!q.isEmpty()) {int len = q.size();while (len > 0) {TreeNode node = q.poll();if(len == 1){res.add(node.val);}if (node.left != null)  q.add(node.left);if (node.right != null) q.add(node.right);len--;}}return res;}
}

完全二叉树的节点个数

很简单, 利用层次遍历计算每一次处理队列中结点的数量即可

class Solution {public int countNodes(TreeNode root) {Queue<TreeNode> q = new LinkedList<>();if(root == null){return 0;}q.add(root);int count=0;while(!q.isEmpty()){int size = q.size();for(int i=0;i<size;i++){TreeNode node = q.poll();count++;if(node.left !=null)q.add(node.left);if(node.right !=null)q.add(node.right);}}return count;}
}
  • 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
  • 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。

二叉树的最大深度

使用层次遍历到最底一层的节点,既可以判断深度大小

class Solution {/*** 迭代法,使用层序遍历*/public int maxDepth(Node root) {if (root == null)   return 0;int depth = 0;Queue<Node> que = new LinkedList<>();que.offer(root);while (!que.isEmpty()){depth ++;int len = que.size();while (len > 0){Node node = que.poll();for (int i = 0; i < node.children.size(); i++)if (node.children.get(i) != null) que.offer(node.children.get(i));len--;}}return depth;}
}
递归用法
class Solution {public int maxDepth(TreeNode root) {//终止条件:当树为空时结束递归,并返回当前深度0if(root == null){return 0;}//root的左、右子树的最大深度int leftDepth = maxDepth(root.left);int rightDepth = maxDepth(root.right);//返回的是左右子树的最大深度+1return Math.max(leftDepth, rightDepth) + 1;}
}

通过二叉树的最大深度递归用法,我们可以总结一下里面递归的过程,比如有一个{1,2,3,4,5,6,7,8}的完全二叉树

1.首先,节点1不为空,进入栈内,然后先调用左子树,这时,节点2入栈,4入栈,8入栈,因为每次都先判断的左子树。

2.这时候栈内又递归调用的maxdepth(8),maxdepth(4),maxdepth(2),maxdepth(1),然后节点8没有左右子树,所以leftdepth和rightdepth都是0,然后返回0+1,这是maxdepth(8)出栈了,回到maxdepth(4),这时候传递的leftdepth=1,因为8时4的左节点,节点4没有右节点,所以rightdepth=0,这是返回1+1,4出栈,返回maxdepth(2),leftdepth=2,rightdepth=1,所以返回2+1,2出栈,返回maxdepth(1),leftdepth=3,进入右子树。。。。

3.最后又返回maxdepth(1),leftdepth=3,rightdepth=2,所以最后的值返回的时3+1

递归调用栈过程
  1. 节点1入栈

    • 调用左子树 maxDepth(2)

  2. 节点2入栈

    • 调用左子树 maxDepth(4)

  3. 节点4入栈

    • 调用左子树 maxDepth(8)

  4. 节点8入栈

    • 左子树为空,返回 0

    • 右子树为空,返回 0

    • 当前节点8的深度为 max(0, 0) + 1 = 1出栈

  5. 回到节点4

    • leftDepth = 1(来自节点8)。

    • 调用右子树 maxDepth(null),返回 0

    • 当前节点4的深度为 max(1, 0) + 1 = 2出栈

  6. 回到节点2

    • leftDepth = 2(来自节点4)。

    • 调用右子树 maxDepth(5)

  7. 节点5入栈

    • 左子树为空,返回 0

    • 右子树为空,返回 0

    • 当前节点5的深度为 max(0, 0) + 1 = 1出栈

  8. 回到节点2

    • rightDepth = 1(来自节点5)。

    • 当前节点2的深度为 max(2, 1) + 1 = 3出栈

  9. 回到节点1

    • leftDepth = 3(来自节点2)。

    • 调用右子树 maxDepth(3)

  10. 节点3入栈

    • 调用左子树 maxDepth(6)

  11. 节点6入栈

    • 左子树为空,返回 0

    • 右子树为空,返回 0

    • 当前节点6的深度为 max(0, 0) + 1 = 1出栈

  12. 回到节点3

    • leftDepth = 1(来自节点6)。

    • 调用右子树 maxDepth(7)

  13. 节点7入栈

    • 左子树为空,返回 0

    • 右子树为空,返回 0

    • 当前节点7的深度为 max(0, 0) + 1 = 1出栈

  14. 回到节点3

    • rightDepth = 1(来自节点7)。

    • 当前节点3的深度为 max(1, 1) + 1 = 2出栈

  15. 回到节点1

    • rightDepth = 2(来自节点3)。

    • 当前节点1的深度为 max(3, 2) + 1 = 4出栈

判断平衡二叉树 

平衡二叉树每个节点的左右两个子树的高度差的绝对值不超过1。而本体并不是考察的深度,而是高度,每一颗二叉子树都要符合高度差的绝对值不超过1

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

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

相关文章

使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第九讲)

这一期讲解GUI_guider中的容器控件的使用以及相关函数&#xff0c;容器本质上是具有布局和自动调整大小功能的基本对象 &#xff0c;通常用来装载其他子控件。 打开上一期的项目&#xff0c;在工具栏中选中容器控件拖拽到界面中&#xff0c;具体如图所示&#xff1a; 容器默认…

qt QGroupButton 实现两个QPushButton的互斥

import sys from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QButtonGroup, QVBoxLayoutclass ExampleApp(QWidget):def __init__(self):super().__init__()self.initUI()def initUI(self):# 创建两个 QPushButtonself.button1 QPushButton("按钮1&quo…

工业物联网的可视化编程革新:Node-RED与边缘计算的深度融合-纵横智控

在工业物联网的演进历程中&#xff0c;可视化编程工具正成为打破技术壁垒的核心力量。Node-RED作为开源的可视化编程平台&#xff0c;通过其独特的拖拽式逻辑构建能力&#xff0c;为设备连接、数据处理与业务逻辑设计提供了全新范式。本文将深入解析Node-RED的技术优势&#xf…

Uniapp:view容器(容器布局)

目录 一、基本概述二、属性说明三、常用布局3.1 横向布局3.2 纵向布局3.3 更多布局3.3.1 纵向布局-自动宽度3.3.2 纵向布局-固定宽度3.3.3 横向布局-自动宽度3.3.4 横向布局-居中3.3.5 横向布局-居右3.3.6 横向布局-平均分布3.3.7 横向布局-两端对齐3.3.8 横向布局-自动填充3.3…

(最新)华为 2026 届校招实习-硬件技术工程师-硬件通用/单板开发—机试题—(共14套)(每套四十题)

&#xff08;最新&#xff09;华为 2026 届校招实习-硬件技术工程师-硬件通用/单板开发—机试题—&#xff08;共14套&#xff09;&#xff08;每套四十题&#xff09; 本套题目为硬件通用题目&#xff0c;适合多个岗位方向&#xff0c;如下 **岗位——硬件技术工程师 岗位意向…

AWS Lambda 架构深入探究

AWS Lambda 是现代云架构中最受欢迎的服务之一,因其能够在完全托管的无服务器环境中运行代码而广受认可。然而,尽管 Lambda 广受欢迎,许多开发者和架构师对它的底层运作机制却知之甚少,常常将其视为“编写能够在云端神奇运行的代码”的简单方法。 本文将探讨 AWS Lambda 背…

Android audio系统五 AudioPolicy 策略配置详解

引用&#xff1a;Android 音频策略配置文件解析流程 audio_policy_configuration.xml 是 Android 音频系统的核心配置文件&#xff0c;它定义了音频硬件接口、设备路由和基本策略。下面我将详细介绍这个文件的结构、关键配置项和实际应用。audio_policy_configuration.xml 是 …

4.21日学习--引用

引用本质&#xff1a;引用的本质在 c 内部实现是一个指针常量。 代码中 int& ref a; 可以理解为 int* const ref &a;&#xff08;指针常量&#xff09;。 指针常量&#xff1a;指针指向不可变&#xff08;绑定 a 后&#xff0c;不能再指向其他变量&#xff09;&…

2.1 数据处理

1. 数据获取方法 掌握公开数据集的使用、数据质量评估指标、了解常见的网络爬虫技术 &#x1f9e9; 一、公开数据集的使用 ✅ 常见平台&#xff08;一定要熟&#xff09; 平台简介示例数据集Hugging Face Datasets专注 NLP、CV 领域的大模型训练数据集库IMDB、SQuAD、Common …

Qt QWidget和QML实现窗口拖动源码分享

一、QWidget实现窗口拖动 .hpp QPoint pressedPoint; bool leftBtnPressed false;.cpp bool PetWidget::eventFilter(QObject *obj, QEvent *event) {if(obj this){if(event->type() QEvent::MouseButtonPress){QMouseEvent* e static_cast<QMouseEvent *>(event)…

在pycharm中搭建yolo11分类检测系统--PyQt5学习(二)

第二部分 测试本地pycharm通过程序连接远程服务器autodl 模型的推理需要借助远程服务器autodl&#xff0c;但是界面的运行是在pycharm中&#xff0c;我的设想是按钮调用一个py文件就好了。 1. 本地运行PyQt5界面。 2. 当需要载入权重时&#xff0c;通过SSH连接到AutodL服务…

前端框架的“快闪“时代:我们该如何应对技术迭代的洪流?

引言&#xff1a;前端开发者的"框架疲劳" “上周刚学完Vue 3的组合式API&#xff0c;这周SolidJS又火了&#xff1f;”——这恐怕是许多前端开发者2023年的真实心声。前端框架的迭代速度已经达到了令人目眩的程度&#xff0c;GitHub每日都有新框架诞生&#xff0c;n…

基于YOLO11的遛狗牵绳识别预警系统

基于YOLO11的遛狗牵绳识别预警系统 【包含内容】 【一】项目提供完整源代码及详细注释 【二】系统设计思路与实现说明 【三】预训练模型与数据集说明 【四】需要列出所有的类别&#xff0c;并且加入识别的类别数量&#xff1a;4类 0: dog (狗) 1: leash (牵引绳) 2: person …

Spring MVC 一个简单的多文件上传

原始代码逐行解释 PostMapping("/uploads") // ① 声明处理POST请求&#xff0c;路径为"/uploads" ResponseBody // ② 直接返回数据到响应体&#xff0c;不进行视图解析 public String uploads(MultipartFile[] files, // …

C++继承(最详细)

目录 1.继承的概念以及定义 1.1 继承的概念 1.2 继承的定义 ​编辑 2.继承中的作用域 3.基类和派生类间的转换 4.派生类的默认成员函数 5.实现不被继承的类 6.継承与友元 ​编辑 7.继承与静态成员 8.多继承及其菱形继承问题 8.2 虚继承 8.3 来看一个小题 9.继承…

day35图像处理OpenCV

文章目录 一、图像预处理17 直方图均衡化17.1绘制直方图17.2直方图均衡化1. 自适应直方图均衡化2. 对比度受限的自适应直方图均衡化3. 示例 19 模板匹配 一、图像预处理 17 直方图均衡化 直方图&#xff1a;反映图像像素分布的统计图&#xff0c;横坐标就是图像像素的取值&…

【音视频】FFmpeg内存模型

FFmpeg内存模型 从现有的Packet拷贝一个新Packet的时候&#xff0c;有两种情况&#xff1a; 两个Packet的buf引用的是同一数据缓存空间&#xff0c;这时候要注意数据缓存空间的释放问题&#xff1b;两个Packet的buf引用不同的数据缓存空间&#xff0c;每个Packet都有数据缓存…

1.2软考系统架构设计师:系统架构的定义与作用 - 练习题附答案及超详细解析

系统架构定义与作用综合知识单选题 题目覆盖核心概念、发展历程、设计原则、评估标准及易混淆点&#xff0c;附答案解析&#xff1a; 1. 系统架构的标准定义源自于以下哪个标准&#xff1f; A. ISO/IEC 9126 B. IEEE 1471-2000 C. TOGAF 9.2 D. ITIL v4 答案&#xff1a;B 简…

go语言对http协议的支持

http&#xff1a;无状态协议&#xff0c;是互联网中使用http使用http实现计算机和计算机之间的请求和响应 使用纯文本方式发送和接受协议数据&#xff0c;不需要借助专门工具进行分析就知道协议中的数据 服务器端的几个概念 Request&#xff1a;用户请求的信息&#xff0c;用…

iscsi服务端安装及配置

1. 安装targetcli软件包 yum install -y targetcli 2. 启动target服务 systemctl start target systemctl enable target 3. 配置防火墙 firewall-cmd --add-port"3260/tcp" 3. 准备一个物理分区&#xff08;或者逻辑分区&#xff09;…