当前位置: 首页 > news >正文

二叉树第一周总结

周一:二叉树的理论基础(Java 版)

本周我们开始讲解二叉树。在《二叉树,你该了解这些!》中讲解了二叉树的理论基础。

有同学会把红黑树和二叉平衡搜索树(Balanced Binary Search Tree)搞混了,其实红黑树就是一种二叉平衡搜索树,它们不是独立的概念。
例如,在 C++ 中 mapmultimapsetmultiset 的底层实现是红黑树。
而在 Java 中,TreeMapTreeSet 的底层实现也是红黑树,所以 Java 的有序集合同样基于平衡二叉搜索树。


✅ Java 中二叉树节点的定义

public class TreeNode {int val;TreeNode left;TreeNode right;// 构造函数:初始化值为 x 的节点,左右子节点默认为 nullTreeNode(int x) {val = x;}// 可选:带左右子节点的构造函数TreeNode(int x, TreeNode left, TreeNode right) {val = x;this.left = left;this.right = right;}
}

🔍 关于构造函数的说明:

有些同学对 TreeNode(int x) { val = x; } 不太理解,这是 Java 中的构造方法。

Java 的类(包括 TreeNode)可以定义构造函数来简化对象创建。

有构造函数时:

TreeNode node = new TreeNode(9); // 简洁明了

没有构造函数时:

TreeNode node = new TreeNode();
node.val = 9;
node.left = null;
node.right = null;

显然更繁琐,所以定义构造函数是良好编程习惯。


🌟 附加知识点:Morris 遍历

在介绍前序、中序、后序遍历时,除了递归和迭代方法外,还有一种高效的遍历方式:Morris 遍历

  • 优点:将非递归遍历的空间复杂度降到 O(1),不需要栈或队列。
  • 原理:利用叶子节点的空指针(left 或 right 为 null)建立临时线索,实现空间优化。
  • 缺点:代码复杂,破坏了树的结构(临时修改指针),结束后需恢复。
  • 面试情况:几乎不考,属于算法进阶内容,感兴趣可自行查阅学习。

⚠️ 提示:我本人也未深入研究 Morris 遍历,建议初学者优先掌握递归与迭代写法。


周二:递归三要素与 N 叉树遍历(Java 版)

在《二叉树:一入递归深似海,从此 offer 是路人》中讲到了递归三要素

  1. 确定递归函数的参数和返回值
  2. 确定终止条件
  3. 确定单层递归的逻辑

掌握了递归思想后,不仅能解决二叉树的前、中、后序遍历,还能轻松扩展到 N 叉树 的遍历问题。

🔧 推荐练习题(LeetCode):

  • 589. N 叉树的前序遍历
  • 590. N 叉树的后序遍历

✅ 提示:N 叉树节点定义如下(Java):

class Node {public int val;public List<Node> children;public Node() {}public Node(int _val) { val = _val; }public Node(int _val, List<Node> _children) {val = _val;children = _children;}
}

前序遍历模板(递归):

public void preorder(Node root, List<Integer> res) {if (root == null) return;res.add(root.val); // 中for (Node child : root.children) {preorder(child, res); // 遍历所有子节点}
}

周三:栈实现迭代遍历(Java 版)

在《二叉树:听说递归能做的,栈也能做!》中,我们开始用模拟递归过程,即所谓的迭代法

有同学发现:前后序遍历中,空节点是否入栈的写法不同。

其实都可以,但空节点不入栈更清晰,也符合动画演示逻辑。


✅ 示例:前序遍历(空节点入栈 vs 不入栈)

❌ 空节点入栈(不推荐,逻辑稍乱)
class Solution {public List<Integer> preorderTraversal(TreeNode root) {Stack<TreeNode> stack = new Stack<>();List<Integer> result = new ArrayList<>();stack.push(root);while (!stack.isEmpty()) {TreeNode node = stack.pop();if (node != null) {result.add(node.val);           // 中stack.push(node.right);         // 右stack.push(node.left);          // 左}// else continue; // 空节点跳过}return result;}
}
✅ 空节点不入栈(推荐,逻辑清晰)
class Solution {public List<Integer> preorderTraversal(TreeNode root) {Stack<TreeNode> stack = new Stack<>();List<Integer> result = new ArrayList<>();if (root == null) return result;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;}
}

🤔 递归 vs 迭代:谁更优?

对比项递归迭代(栈模拟)
时间复杂度O(n)O(n)
空间复杂度O(h),h 为树高(系统栈开销)O(h),手动维护栈
可读性高,逻辑清晰中,需要理解栈操作
安全性低,深度大时可能栈溢出高,可控性强
实际开发建议尽量避免,尤其在参数复杂、调用深时推荐用于生产环境,更稳定

💡 总结:递归方便程序员,难为了机器;迭代难为了程序员,方便了机器。


周四:统一写法的迭代遍历(Java 版)

在《二叉树:前中后序迭代方式的写法就不能统一一下么?》中,我们使用空节点标记法实现了前、中、后序遍历的统一写法。

例如:中序遍历统一写法

class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();Stack<TreeNode> stack = new Stack<>();if (root != null) stack.push(root);while (!stack.isEmpty()) {TreeNode node = stack.pop();if (node != null) {// 按照 “右 -> 中 -> null -> 左” 顺序入栈if (node.right != null) stack.push(node.right);stack.push(node);stack.push(null); // 标记中间节点if (node.left != null) stack.push(node.left);} else {// 遇到 null,说明下一个节点是需要处理的中间节点node = stack.pop();result.add(node.val);}}return result;}
}

三种迭代遍历:

/ 前序遍历顺序:中-左-右,入栈顺序:中-右-左
class Solution {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;}
}// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
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;}
}

❓ 是否必须使用统一写法?

不需要! 哪种写法你记得住、写得顺,就用哪种。

但必须掌握至少一种迭代写法,因为面试官常会在你写出递归后追问:“能写成迭代吗?”


周五:层序遍历(广度优先搜索)

在《二叉树:层序遍历登场!》中,我们介绍了层序遍历,即图论中的**广度优先搜索(BFS)**在二叉树上的应用。

✅ Java 层序遍历模板

class Solution {public List<List<Integer>> levelOrder(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;}
}

✅ 推荐练习题:

  1. 102. 二叉树的层序遍历
  2. 107. 二叉树的层序遍历 II(从下到上)
  3. 199. 二叉树的右视图
  4. 637. 二叉树的层平均值
  5. 429. N 叉树的层序遍历
  6. 515. 在每个树行中找最大值

✅ 技巧:层序遍历的框架固定,只需在每层遍历时添加特定逻辑即可。


周六:翻转二叉树(Java 版)

在《二叉树:你真的会翻转二叉树么?》中,我们剖析了这道经典题目。

❌ 递归中序遍历的问题

直接使用标准中序遍历(左 → 中 → 右)会导致某些节点被翻转两次:

// 错误示例:会导致重复翻转
void invert(TreeNode root) {if (root == null) return;invert(root.left);           // 左swap(root.left, root.right); // 中invert(root.right);          // 右 → 此时 right 是原来的 left,又被翻转一次!
}

✅ 修正版中序递归(非常规写法)

class Solution {public TreeNode invertTree(TreeNode root) {if (root == null) return null;invertTree(root.left);                    // 先翻转左子树swapChildren(root);                       // 翻转当前节点invertTree(root.left); // 注意!这里仍是 left,因为刚被 swap 过return root;}private void swapChildren(TreeNode root) {TreeNode temp = root.left;root.left = root.right;root.right = temp;}
}

⚠️ 虽然能工作,但这已不是标准中序逻辑,容易混淆,不推荐


✅ 迭代法中序统一写法(可行)

使用栈 + 空节点标记法,可以安全实现中序翻转:

class Solution {public TreeNode invertTree(TreeNode root) {Stack<TreeNode> stack = new Stack<>();if (root != null) stack.push(root);while (!stack.isEmpty()) {TreeNode node = stack.pop();if (node != null) {// 按右 -> 中 -> null -> 左 入栈if (node.right != null) stack.push(node.right);stack.push(node);stack.push(null);if (node.left != null) stack.push(node.left);} else {// 处理中间节点node = stack.pop();swapChildren(node); // 翻转左右子树}}return root;}private void swapChildren(TreeNode root) {TreeNode temp = root.left;root.left = root.right;root.right = temp;}
}

✅ 为什么可以?因为是用栈控制访问顺序,而不是靠指针移动,避免了重复翻转。


总结:本周知识图谱(Java 版)

本周我们系统学习了二叉树:

  1. 理论基础:二叉树种类、存储方式、遍历方式
  2. 遍历方法
    • 递归(前/中/后序)
    • 迭代(栈模拟,统一与非统一写法)
    • 层序遍历(队列,BFS)
  3. 核心思想
    • 递归三要素
    • 栈与队列的应用
    • 指针与引用的操作
  4. 经典题目:翻转二叉树,综合运用各种遍历方式

学习建议

  • 先掌握递归写法(易理解)
  • 再学习迭代写法(面试加分)
  • 最后尝试统一写法或 Morris 遍历(进阶)
  • 多刷题,形成“遍历 + 处理”的思维模式
http://www.dtcms.com/a/420249.html

相关文章:

  • 湖北商城网站建设多少钱用手机可以做网站吗
  • 惠州微网站建设网站建设公司位置
  • 个人网站可以做百度推广吗wordpress后台多媒体不显示缩
  • istio 部署
  • 专业做营销网站哪个平台查企业免费
  • 【故障】win7命令行窗口cmd闪退
  • 优化网站加载速度宿迁市建设局网站维修基金
  • Spring IOC源码篇六 核心方法obtainFreshBeanFactory.parseCustomElement
  • 【c++】红黑树的部分实现
  • cpp02:类和对象
  • 即墨城乡建设局网站金融网站建设报价方案
  • 公司网站建设的建议电商网站建设期末考试
  • 自己的做网站cms资源
  • 图像采集卡:连接镜头与机器的“视觉神经”,释放工业智能核心动力
  • 北京移动端网站多少钱wordpress 首页跳转
  • 大连网站平台研发创客贴做网站吗
  • 如何撤销网站备案注册公司材料怎么准备
  • 学生可做的网站主题微信公众上传wordpress
  • 延吉建设局网站wordpress门户加商城
  • 高并发系统的高可用架构
  • 怎么自己开一个网站当当网网站开发计划和预算
  • 网站建设可行性报告范文网站建设 投资合作
  • 惠城网站建设有哪些做财经类新闻的网站
  • 有自己的网站做淘宝联盟号做吗房地产网站开发文档
  • 连云港公司企业网站建设网站创建人
  • C++ 并发编程最佳实践详解
  • 大型网站怎样做优化PHP国内优秀vi设计案例
  • 【论文精读】Few-Shot Object Detection with Attention-RPN and Multi-Relation Detector
  • 山东建设和城乡建设厅注册中心网站驾校视频网站模板
  • 莱芜住房和城乡建设厅网站专业网站建设哪家好