JAVA数据结构 DAY7-二叉树

本系列可作为JAVA学习系列的笔记,文中提到的一些练习的代码,小编会将代码复制下来,大家复制下来就可以练习了,方便大家学习。

点赞关注不迷路!您的点赞、关注和收藏是对小编最大的支持和鼓励!

系列文章目录

JAVA初阶---------已更完

JAVA数据结构 DAY1-集合和时空复杂度

JAVA数据结构 DAY2-包装类和泛型

JAVA数据结构 DAY3-List接口

JAVA数据结构 DAY4-ArrayList

JAVA数据结构 DAY5-LinkedList

JAVA数据结构 DAY6-栈和队列

JAVA数据结构 DAY7-二叉树


拓展目录

手把手教你用 ArrayList 实现杨辉三角:从逻辑推导到每行代码详解

链表高频 6 题精讲 | 从入门到熟练掌握链表操作


目录

目录

系列文章目录

拓展目录

目录

前言

本节学习目标

一、树形结构:二叉树的前置基础(了解,重点掌握核心概念)

[1.1 树的核心概念](#1.1 树的核心概念)

树的判定规则(树与非树的核心区别)

[1.2 树的核心术语(重要,熟记 + 会判断,面试高频考点)](#1.2 树的核心术语(重要,熟记 + 会判断,面试高频考点))

[1.3 树的表示形式(了解,重点掌握孩子兄弟表示法)](#1.3 树的表示形式(了解,重点掌握孩子兄弟表示法))

[孩子兄弟表示法的结点定义(伪代码,Java 风格)](#孩子兄弟表示法的结点定义(伪代码,Java 风格))

孩子兄弟表示法的存储逻辑

其他表示法(仅作了解)

[1.4 树的实际应用](#1.4 树的实际应用)

二、二叉树:核心重点知识(必须掌握,面试核心)

[2.1 二叉树的核心概念](#2.1 二叉树的核心概念)

[2.1.1 二叉树的正式定义](#2.1.1 二叉树的正式定义)

[2.1.2 二叉树的核心特性(与普通树的本质区别)](#2.1.2 二叉树的核心特性(与普通树的本质区别))

[2.1.3 二叉树的五种基本形态](#2.1.3 二叉树的五种基本形态)

[2.2 两种特殊的二叉树:满二叉树 & 完全二叉树(面试高频考点)](#2.2 两种特殊的二叉树:满二叉树 & 完全二叉树(面试高频考点))

[2.2.1 满二叉树](#2.2.1 满二叉树)

定义

核心特征

[2.2.2 完全二叉树](#2.2.2 完全二叉树)

定义

通俗理解

核心特征(面试必考,熟记)

[2.2.3 满二叉树与完全二叉树的核心对比](#2.2.3 满二叉树与完全二叉树的核心对比)

[2.3 二叉树的五大核心性质(必背 + 会用,面试计算题核心)](#2.3 二叉树的五大核心性质(必背 + 会用,面试计算题核心))

[性质 1:第 i 层的最大结点数](#性质 1:第 i 层的最大结点数)

[性质 2:深度为 K 的二叉树的最大结点总数](#性质 2:深度为 K 的二叉树的最大结点总数)

[性质 3:叶子结点与度为 2 的结点的数量关系](#性质 3:叶子结点与度为 2 的结点的数量关系)

[性质 4:完全二叉树的深度计算](#性质 4:完全二叉树的深度计算)

[性质 5:完全二叉树的结点编号关系](#性质 5:完全二叉树的结点编号关系)

[2.4 二叉树性质经典计算题(附答案 + 详细解析,面试必考)](#2.4 二叉树性质经典计算题(附答案 + 详细解析,面试必考))

[例题 1](#例题 1)

[例题 2](#例题 2)

[例题 3](#例题 3)

[例题 4](#例题 4)

[2.5 二叉树的存储结构](#2.5 二叉树的存储结构)

[2.5.1 顺序存储](#2.5.1 顺序存储)

定义

存储规则

存储示例

优缺点

应用场景

[2.5.2 链式存储](#2.5.2 链式存储)

定义

[方式 1:孩子表示法(二叉表示法,最常用)](#方式 1:孩子表示法(二叉表示法,最常用))

结点结构

[结点定义(Java 代码)](#结点定义(Java 代码))

核心优缺点

[方式 2:孩子双亲表示法(三叉表示法)](#方式 2:孩子双亲表示法(三叉表示法))

结点结构

[结点定义(Java 代码)](#结点定义(Java 代码))

核心优缺点

应用场景

三、二叉树的基本操作(核心应用,递归实现为主)

[3.1 二叉树的手动创建(入门阶段,非实际开发方式)](#3.1 二叉树的手动创建(入门阶段,非实际开发方式))

[3.1.1 前置说明](#3.1.1 前置说明)

[3.1.2 完整代码实现(Java,基于孩子表示法)](#3.1.2 完整代码实现(Java,基于孩子表示法))

[3.2 二叉树的遍历:最核心的操作(所有操作的基础,面试必考)](#3.2 二叉树的遍历:最核心的操作(所有操作的基础,面试必考))

[3.2.1 遍历的定义与意义](#3.2.1 遍历的定义与意义)

[3.2.2 前序、中序、后序遍历(深度优先,递归实现)](#3.2.2 前序、中序、后序遍历(深度优先,递归实现))

[方式 1:前序遍历(NLR:根 → 左 → 右)](#方式 1:前序遍历(NLR:根 → 左 → 右))

遍历规则

遍历步骤(详细)

遍历结果

[代码实现(在 BinaryTree 类中添加方法)](#代码实现(在 BinaryTree 类中添加方法))

[方式 2:中序遍历(LNR:左 → 根 → 右)](#方式 2:中序遍历(LNR:左 → 根 → 右))

遍历规则

遍历结果

[代码实现(在 BinaryTree 类中添加方法)](#代码实现(在 BinaryTree 类中添加方法))

[方式 3:后序遍历(LRN:左 → 右 → 根)](#方式 3:后序遍历(LRN:左 → 右 → 根))

遍历规则

注意事项

遍历结果

[代码实现(在 BinaryTree 类中添加方法)](#代码实现(在 BinaryTree 类中添加方法))

[前 / 中 / 后序遍历的核心对比](#前 / 中 / 后序遍历的核心对比)

[3.2.3 层序遍历(广度优先,借助队列实现)](#3.2.3 层序遍历(广度优先,借助队列实现))

遍历规则

实现思路(核心,熟记)

遍历结果(基于手动创建的二叉树)

[代码实现(在 BinaryTree 类中添加方法,需导入队列包)](#代码实现(在 BinaryTree 类中添加方法,需导入队列包))

测试所有遍历方法

[3.2.4 遍历推导经典选择题(附答案 + 深度解析)](#3.2.4 遍历推导经典选择题(附答案 + 深度解析))

[例题 1](#例题 1)

[例题 2](#例题 2)

[例题 3](#例题 3)

[例题 4](#例题 4)

[3.3 二叉树的常用基本操作(递归实现 + 详细思路)](#3.3 二叉树的常用基本操作(递归实现 + 详细思路))

[3.3.1 获取树中节点的个数(int size(Node root))](#3.3.1 获取树中节点的个数(int size(Node root)))

[3.3.2 获取叶子节点的个数(int getLeafNodeCount(Node root))](#3.3.2 获取叶子节点的个数(int getLeafNodeCount(Node root)))

[3.3.3 获取第 K 层节点的个数(int getKLevelNodeCount(Node root,int k))](#3.3.3 获取第 K 层节点的个数(int getKLevelNodeCount(Node root,int k)))

[3.3.4 获取二叉树的高度(int getHeight(Node root))](#3.3.4 获取二叉树的高度(int getHeight(Node root)))

[3.3.5 检测值为 value 的元素是否存在(Node find(Node root, int val))](#3.3.5 检测值为 value 的元素是否存在(Node find(Node root, int val)))

[3.3.6 判断一棵树是不是完全二叉树(boolean isCompleteTree(Node root))](#3.3.6 判断一棵树是不是完全二叉树(boolean isCompleteTree(Node root)))

[四、二叉树经典 OJ 题汇总(实战练习 + 解题思路)](#四、二叉树经典 OJ 题汇总(实战练习 + 解题思路))

[4.1 基础 OJ 题(掌握核心操作即可解决)](#4.1 基础 OJ 题(掌握核心操作即可解决))

[1. 检查两颗树是否相同](#1. 检查两颗树是否相同)

[2. 另一颗树的子树](#2. 另一颗树的子树)

[3. 翻转二叉树](#3. 翻转二叉树)

[4. 对称二叉树](#4. 对称二叉树)

[5. 二叉树的分层遍历](#5. 二叉树的分层遍历)

[4.2 进阶 OJ 题(需结合性质 / 遍历推导)](#4.2 进阶 OJ 题(需结合性质 / 遍历推导))

[6. 二叉树的构建及遍历](#6. 二叉树的构建及遍历)

[7. 给定一个二叉树,找到该树中两个指定节点的最近公共祖先](#7. 给定一个二叉树,找到该树中两个指定节点的最近公共祖先)

[8. 根据一棵树的前序遍历与中序遍历构造二叉树](#8. 根据一棵树的前序遍历与中序遍历构造二叉树)

[9. 根据一棵树的中序遍历与后序遍历构造二叉树(课后作业)](#9. 根据一棵树的中序遍历与后序遍历构造二叉树(课后作业))

[10. 二叉树创建字符串](#10. 二叉树创建字符串)

[11-13. 二叉树前序 / 中序 / 后序非递归遍历实现](#11-13. 二叉树前序 / 中序 / 后序非递归遍历实现)

[14. 判断一颗二叉树是否是平衡二叉树](#14. 判断一颗二叉树是否是平衡二叉树)

[4.3 练习建议](#4.3 练习建议)

五、学习总结与备考指南

[5.1 核心知识点回顾](#5.1 核心知识点回顾)

[5.2 面试备考建议](#5.2 面试备考建议)

[5.3 拓展学习](#5.3 拓展学习)


前言

小编作为新晋码农一枚,会定期整理一些写的比较好的代码,作为自己的学习笔记,会试着做一下批注和补充,如转载或者参考他人文献会标明出处,非商用,如有侵权会删改!欢迎大家斧正和讨论!

在数据结构的知识体系中,二叉树是非线性结构 的核心,更是连接线性结构(数组、链表)与复杂树形结构(平衡树、红黑树、B 树 / B + 树)的关键桥梁。它不仅是大学计算机专业的核心考点,更是互联网大厂算法面试的高频必考内容,掌握二叉树的所有知识点,是学好数据结构与算法的必经之路。

本文将基于二叉树的核心学习资料,进行全内容、超详细、重逻辑的梳理,从树形结构的基础概念入手,逐步拆解二叉树的定义、特性、存储结构、基本操作,结合经典例题、遍历推导题和 OJ 实战题做深度解析,覆盖从入门到面试的所有考点,既是新手入门的保姆级教程,也是面试复习的一站式指南。

本节学习目标

  1. 理解并掌握树的基本概念、核心术语及判定规则,建立非线性结构思维
  2. 吃透二叉树的定义、核心特性,区分满二叉树与完全二叉树的本质区别
  3. 熟记二叉树五大核心性质,能灵活解决各类计算型考题
  4. 掌握二叉树的两种存储结构,理解链式存储的实现逻辑
  5. 精通二叉树的遍历操作(前 / 中 / 后序、层序),能实现递归遍历并推导遍历序列
  6. 掌握二叉树的常用基本操作,理解递归实现的核心思路
  7. 能独立完成二叉树相关的概念题、遍历推导题,并掌握经典 OJ 题的解题方向

一、树形结构:二叉树的前置基础(了解,重点掌握核心概念)

在学习二叉树之前,我们需要先掌握树形结构的基本概念,这是理解二叉树的前提。树形结构是一种典型的非线性数据结构,与数组、链表等 "一对一" 的线性结构不同,其节点之间呈现 "一对多" 的层次关系,更贴近现实世界中的层级化场景(如文件目录、家族族谱)。

1.1 树的核心概念

树是由n(n≥0)个有限结点 组成的一个具有层次关系 的集合。因其结构形似 "倒挂的树"(根朝上、叶朝下)而得名,当 n=0 时,称为空树;当 n>0 时,为非空树,且必须满足以下 4 个核心特点:

  1. 存在唯一的根结点:有一个特殊的结点,称为根结点,根结点没有前驱结点,是整棵树的起始点;
  2. 子树互不相交 :除根结点外,其余结点被分成 M(M>0)个互不相交的集合 T1、T2、......、Tm,其中每一个集合(1≤i≤m)又是一棵与原树类似的子树
  3. 子树根节点的唯一性:每棵子树的根结点有且只有一个前驱结点,但可以有 0 个或多个后继结点;
  4. 递归定义 :树的定义是递归式的,即树由根结点和若干棵子树组成,而每一棵子树又可以看作是一棵新的树,递归是树和二叉树操作的核心思想。
树的判定规则(树与非树的核心区别)

一个结构若要被判定为,必须同时满足以下 3 个条件,缺一不可,否则即为非树结构:

  1. 子树之间绝对不能有交集
  2. 除根结点外,每个结点有且仅有一个父结点
  3. 一棵有 N 个结点的树,必然有 N-1 条边(除根结点外,每个结点都通过一条边连接到其父结点,无多余边、无缺失边)。

1.2 树的核心术语(重要,熟记 + 会判断,面试高频考点)

学习树和二叉树,必须掌握其核心术语,其中标★的为高频考点 ,其余术语仅作了解,能理解含义即可。以下结合典型树结构(根为 A,A 有 B、C、D、E、F、G6 个子结点;D 有子结点 H;E 有子结点 K、L;F 有子结点 M、N;G 有子结点 P)逐一讲解,结合实例更易记忆:

表格

术语 定义 示例(基于上述树结构) 考点等级
★结点的度 一个结点含有子树的个数(即子结点的数量) A 的度为 6,D 的度为 1,H 的度为 0 高频
★树的度 一棵树中,所有结点度的最大值 上述树的度为 6(A 的度最大) 高频
★叶子结点(终端结点) 度为 0的结点(无任何子结点,是树的最底层结点) B、C、H、K、L、M、N、P 高频
双亲结点(父结点) 若一个结点含有子结点,则这个结点称为其子结点的父结点 A 是 B 的父结点,D 是 H 的父结点 基础
孩子结点(子结点) 一个结点含有的子树的根结点称为该结点的子结点 B 是 A 的孩子结点,H 是 D 的孩子结点 基础
★根结点 一棵树中,没有双亲结点的结点,非空树有且仅有一个 上述树的根结点为 A 高频
★结点的层次 从根开始定义,根为第 1 层,根的子结点为第 2 层,依次类推 A(1 层)、B(2 层)、H(3 层)、K(3 层) 高频
★树的高度 / 深度 树中结点的最大层次 上述树的最大层次为 4,高度 / 深度为 4 高频
非终端结点(分支结点) 度不为 0的结点(除叶子结点外的所有结点) A、D、E、F、G 了解
兄弟结点 具有相同父结点的结点互称为兄弟结点 B、C、D 互为兄弟结点,K、L 互为兄弟结点 了解
堂兄弟结点 双亲在同一层的结点互为堂兄弟结点 H(父 D 在 2 层)、K(父 E 在 2 层)互为堂兄弟结点 了解
结点的祖先 根结点到该结点 所经分支上的所有结点 H 的祖先为 A、D;所有结点的祖先均为 A 了解
子孙 以某结点为根的子树中任一结点都称为该结点的子孙 E 的子孙为 K、L;A 的子孙为所有结点 了解
森林 m(m≥0)棵互不相交的树组成的集合 上述树去掉根结点 A 后,6 棵子树(B、C、D、E、F、G)构成一个森林 了解

1.3 树的表示形式(了解,重点掌握孩子兄弟表示法)

树的节点之间是 "一对多" 的关系,其存储表示比线性结构复杂,实际开发中根据需求不同,有双亲表示法、孩子表示法、孩子双亲表示法、孩子兄弟表示法 等多种方式,我们只需重点了解最常用的孩子兄弟表示法,其余表示法仅作概念认知即可。

孩子兄弟表示法是将普通树转换为二叉树的核心方式,其核心思路是:将树的 "一对多" 关系转换为 "一对一" 的链式关系,为每个结点设置三个域:

  1. 数据域:存储树中结点的实际数据;
  2. 第一个孩子引用 :存储该结点最左侧子结点的地址;
  3. 下一个兄弟引用 :存储该结点右侧相邻兄弟结点的地址。
孩子兄弟表示法的结点定义(伪代码,Java 风格)
java 复制代码
class Node {
    int value;        // 树中存储的数据
    Node firstChild;  // 指向第一个孩子结点的引用
    Node nextBrother; // 指向下一个兄弟结点的引用
}
孩子兄弟表示法的存储逻辑

以根结点 A 为例:

  • A 的firstChild指向其第一个孩子 B,nextBrother为 null(根结点无兄弟结点);
  • B 的firstChild为 null(B 是叶子结点),nextBrother指向其右侧兄弟 C;
  • C 的firstChild为 null,nextBrother指向其右侧兄弟 D;
  • D 的firstChild指向其孩子 H,nextBrother指向其右侧兄弟 E;
  • 以此类推,直到最右侧结点 G 的nextBrother为 null。
其他表示法(仅作了解)
  1. 双亲表示法:用数组存储所有结点,每个结点包含 "数据域 + 双亲域(父结点在数组中的下标)",便于快速查找父结点,但查找子结点需要遍历整个数组;
  2. 孩子表示法 :为每个结点设置一个孩子链表,存储该结点的所有子结点地址,便于快速查找子结点,但查找父结点需要遍历所有孩子链表;
  3. 孩子双亲表示法:结合双亲表示法和孩子表示法,每个结点同时存储父结点下标和孩子链表,兼顾父 / 子结点的查找效率,但存储开销更大,结构更复杂。

1.4 树的实际应用

树形结构的 "层次化、一对多" 特性,使其能完美抽象现实世界中的层级化关系,实际开发中应用广泛,最典型的场景是文件系统管理(目录和文件)

  • 操作系统的文件目录结构中,根目录 为树的根结点,子目录 为树的分支结点,文件为树的叶子结点;
  • 目录可以嵌套子目录,对应树的分支结点可以有子结点,文件无法嵌套,对应叶子结点无子女结点,完全契合树形结构的特点。

除此之外,树形结构还应用于:

  1. 企业 / 部门的组织架构管理(最高管理者为根,各级管理者为分支结点,基层员工为叶子结点);
  2. 数据库的索引结构(B 树、B + 树,提升数据查询效率);
  3. 编译原理的表达式树、语法树
  4. 人工智能的决策树(机器学习经典算法);
  5. 数据压缩的哈夫曼树等。

二、二叉树:核心重点知识(必须掌握,面试核心)

二叉树是树形结构中最简单、最核心 的类型,也是算法面试的重中之重。其结构有严格的约束性,操作均基于递归思想实现,是后续学习平衡树、红黑树、B 树等复杂树形结构的基础。

2.1 二叉树的核心概念

2.1.1 二叉树的正式定义

一棵二叉树是结点的一个有限集合,该集合满足以下两种情况之一

  1. 空集 :即二叉树中没有任何结点,称为空二叉树(n=0);
  2. 非空集 :由一个根结点 加上两棵分别称为左子树右子树互不相交的二叉树组成。

从定义可以看出,二叉树的定义也是递归式 的 ------ 非空二叉树的左子树和右子树本身也是二叉树,这一特性决定了二叉树的绝大多数操作都可以通过递归实现。

2.1.2 二叉树的核心特性(与普通树的本质区别)

二叉树与普通树的核心区别在于结构的约束性,从定义可推导出两个核心特征,也是二叉树的本质属性,必须牢牢掌握:

  1. 二叉树中不存在度大于 2 的结点 :二叉树的每个结点的度只能是0(叶子结点)、1(仅有左孩子或仅有右孩子)、2(既有左孩子又有右孩子),这一约束让二叉树的结构比普通树更简单,操作更易实现;
  2. 二叉树的子树有严格的左右之分,次序不能颠倒 :即使一个结点只有一棵子树,也必须明确其是左子树 还是右子树 ,因此二叉树是有序树;而普通树的子树没有左右之分,是无序树。

举例 :一个结点只有一个孩子结点,在二叉树中分为 "仅有左孩子" 和 "仅有右孩子"两棵完全不同的二叉树;而在普通树中,这只是同一种情况。

2.1.3 二叉树的五种基本形态

任意复杂的二叉树,都是由以下五种基本形态复合而成的,这是理解二叉树结构的基础,不存在其他额外的基础形态:

  1. 空二叉树:无任何结点,是二叉树的基础形态;
  2. 只有根结点:仅有一个根结点,无左子树和右子树;
  3. 只有左子树:根结点加上一棵非空的左子树,右子树为空;
  4. 只有右子树:根结点加上一棵非空的右子树,左子树为空;
  5. 左右子树均存在:根结点加上一棵非空的左子树和一棵非空的右子树。

2.2 两种特殊的二叉树:满二叉树 & 完全二叉树(面试高频考点)

二叉树中有两种特殊类型,具有良好的结构性质,是面试必考内容 ,其中完全二叉树 是实际开发中应用最广泛的二叉树(如堆的实现),满二叉树 是完全二叉树的特殊子集

2.2.1 满二叉树
定义

一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树。也就是说,若一棵二叉树的层数为 K,且结点总数为2K−1(K≥1,根结点为第 1 层),则它就是满二叉树。

核心特征
  1. 满二叉树中不存在度为 1 的结点,所有结点的度只能是 0(叶子结点)或 2(分支结点);
  2. 所有叶子结点都集中在最底层(第 K 层),上层所有结点均为分支结点;
  3. 结点总数固定为2K−1,例如:
    • K=1 时,结点总数 = 1(仅根结点);
    • K=2 时,结点总数 = 3(根 + 左孩子 + 右孩子);
    • K=3 时,结点总数 = 7(根 + 2 个二级结点 + 4 个三级结点);
    • K=4 时,结点总数 = 15,以此类推;
  4. 满二叉树的叶子结点数为2K−1(即最底层的最大结点数)。
2.2.2 完全二叉树
定义

完全二叉树是由满二叉树引申而来的效率极高 的数据结构,是实际开发中最常用的二叉树类型。对于深度为 K 的、有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 0 至 n-1 的结点一一对应时,称之为完全二叉树(编号规则:自上而下、自左至右)。

通俗理解

完全二叉树的结点填充遵循 **"自上而下、自左至右"** 的原则,不允许跳过任何一个位置

  1. 除最后一层外,其余各层的结点数都达到最大值
  2. 最后一层的结点集中在左侧 ,右侧可以有空缺,但绝对不允许左侧空缺而右侧有结点
核心特征(面试必考,熟记)
  1. 满二叉树是一种特殊的完全二叉树 :当完全二叉树的结点总数为2K−1时,其就是满二叉树,因此满二叉树一定是完全二叉树,但完全二叉树不一定是满二叉树
  2. 度为 1 的结点个数只能是 0 或 1 :这是完全二叉树最核心、最常用的性质,面试必考。原因是结点按 "左到右" 填充,仅有一个孩子的结点只能是最后一个分支结点,且其孩子一定是左孩子
  3. 完全二叉树的高度是具有相同结点数的二叉树中最小的,因此查找、插入、删除等操作的效率更高;
  4. 叶子结点主要集中在最底层 ,若有度为 1 的结点,那么会有少量叶子结点出现在倒数第二层
2.2.3 满二叉树与完全二叉树的核心对比

为了更清晰地区分两者,从结点填充、度为 1 的结点数、结点总数等核心维度做对比,便于记忆和判断:

对比维度 满二叉树 完全二叉树
结点填充规则 所有层结点数均为最大值,无任何空缺 除最后一层外,其余层结点数为最大值;最后一层左填右空,无中间空缺
度为 1 的结点数 固定为 0 只能是 0 或 1(面试核心)
结点总数 必为2K−1(K 为层数 / 深度) 范围为2K−1≤n≤2K−1(K 为深度)
叶子结点位置 全部集中在最底层(第 K 层) 主要在最底层,少数可在倒数第二层(度为 1 时)
包含关系 是完全二叉树的特殊子集 包含所有满二叉树
空树判定 空树是特殊的满二叉树 空树是特殊的完全二叉树

2.3 二叉树的五大核心性质(必背 + 会用,面试计算题核心)

二叉树的五大性质是解决概念题、计算型考题 的理论基础,也是面试的高频考点,必须熟记并能灵活推导、应用 。所有性质均遵循教材通用约定

  • 根结点的层数为1
  • 二叉树的深度(高度)为结点的最大层次
  • 完全二叉树的结点编号从0开始(自上而下、自左至右)。
性质 1:第 i 层的最大结点数

若规定根结点的层数为 1,则一棵非空二叉树的第 i 层上最多有2i−1(i>0)个结点

推导与举例

  • i=1 时,第 1 层只有根结点,结点数 = 1=20,符合公式;
  • i=2 时,第 2 层最多有 2 个结点,结点数 = 2=21,符合公式;
  • i=3 时,第 3 层最多有 4 个结点,结点数 = 4=22,符合公式;
  • 本质:第 i 层的最大结点数是一个首项为 1、公比为 2的等比数列,第 i 项为2i−1。

应用:快速计算二叉树某一层的最大结点数,例如求第 6 层的最大结点数:25=32个。

性质 2:深度为 K 的二叉树的最大结点总数

若规定只有根结点的二叉树的深度为 1,则深度为 K 的二叉树的最大结点数是2K−1(k≥0)

推导过程 :深度为 K 的二叉树的最大结点数,即每一层的结点数都达到最大值,将各层的最大结点数求和即可:总结点数=20+21+22+⋯+2K−1这是一个首项为 1、公比为 2、项数为 K 的等比数列,根据等比数列求和公式Sn​=1−qa1​(1−qn)​,代入得:SK​=1−21×(1−2K)​=2K−1

特殊情况

  • K=0 时,为空二叉树,结点数 = 0,符合20−1=0;
  • K=1 时,结点数 = 1,符合21−1=1。

应用 :快速判断一棵二叉树是否为满二叉树------ 若深度为 K 的二叉树的结点总数为2K−1,则该二叉树为满二叉树。

性质 3:叶子结点与度为 2 的结点的数量关系

任何一棵二叉树,如果其叶结点个数为 n0,度为 2 的非叶结点个数为 n2,则有 **n0=n2+1**。

这是二叉树最核心、最常用的性质,面试必考,必须熟记并能推导

推导过程 :设二叉树的结点总数为 n,度为 1 的结点个数为 n1,则结点总数满足:n=n0+n1+n2(1)根据树的基本结论:一棵有 n 个结点的树,必有 n-1 条边 。而二叉树的边数又可以通过结点的度来计算(度为 0 的结点贡献 0 条边,度为 1 的结点贡献 1 条边,度为 2 的结点贡献 2 条边),因此边数满足:n−1=0×n0+1×n1+2×n2(2)将公式 (1) 代入公式 (2),化简后可得:n0=n2+1

核心结论任意二叉树的叶子结点数,始终比度为 2 的结点数多 1,与度为 1 的结点数(n1)无关。

应用:已知度为 2 的结点数,可直接求叶子结点数,反之亦然。例如某二叉树有 100 个度为 2 的结点,则其叶子结点数 = 100+1=101 个。

性质 4:完全二叉树的深度计算

具有 n 个结点的完全二叉树的深度(高度)k 为 **log2​(n+1)的上取整 **,也可等价表示为 **log2​n的下取整 + 1**,两种表示方式的结果完全一致,可根据题目需求选择使用。

推导过程 :设完全二叉树的深度为 k,根据完全二叉树的特征,其结点数 n 满足:2k−1≤n≤2k−1对不等式两边同时加 1 ,得:2k−1+1≤n+1≤2k对不等式取以 2 为底的对数,得:k−1<log2​(n+1)≤k因此,k 为log2​(n+1)向上取整,即k=⌈log2​(n+1)⌉。

举例:求有 531 个结点的完全二叉树的深度:

  • 方法 1:log2(531+1)=log2532≈9.05,向上取整 = 10;
  • 方法 2:log2531≈9.05,向下取整 = 9,9+1=10。
性质 5:完全二叉树的结点编号关系

对于具有 n 个结点的完全二叉树 ,如果按照自上而下、自左至右 的顺序对所有节点从 0 开始编号,则对于序号为 i 的结点有以下 3 条规则(完全二叉树顺序存储的基础,面试必考):

  1. 双亲结点序号:若 i>0,双亲序号为(i−1)/2(整数除法,向下取整);若 i=0,则为根结点,无双亲结点;
  2. 左孩子结点序号:若2i+1<n,则左孩子序号为 2i+1,否则无左孩子;
  3. 右孩子结点序号:若2i+2<n,则右孩子序号为 2i+2,否则无右孩子。

补充 :若教材 / 题目中约定编号从 1 开始,则规则更简洁,可快速转换:

  1. 双亲结点序号:i>1 时,双亲序号为⌊i/2⌋;i=1 为根结点,无双亲;
  2. 左孩子结点序号:2i≤n时,左孩子序号为 2i,否则无左孩子;
  3. 右孩子结点序号:2i+1≤n时,右孩子序号为 2i+1,否则无右孩子。

举例:一棵有 10 个结点的完全二叉树,编号为 5 的结点(i=5):

  • 双亲序号:(5-1)/2=2;
  • 左孩子序号:2×5+1=11(11≥10,无左孩子);
  • 右孩子序号:2×5+2=12(12≥10,无右孩子);
  • 结论:该结点为叶子结点

应用 :完全二叉树的顺序存储(数组存储),通过结点的编号(数组下标)可快速定位其父结点和孩子结点,无需存储指针,** 堆(大根堆 / 小根堆)** 的实现就是基于这一性质。

2.4 二叉树性质经典计算题(附答案 + 详细解析,面试必考)

结合二叉树的五大性质,以下 4 道题是面试高频计算型考题,覆盖了性质 3、4、5 的核心应用,必须独立完成并理解解析过程:

例题 1

某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为()A 不存在这样的二叉树 B 200 C 198 D 199

解析

  1. 根据性质 3:n0=n2+1,已知 n2=199,则 n0=199+1=200;
  2. 验证结点总数:n = n0 + n1 + n2 = 200 + n1 + 199 = 399 + n1;
  3. 已知二叉树总结点数为 399,因此 n1=0(度为 1 的结点数为 0),该二叉树存在。答案:B
例题 2

在具有 2n 个结点的完全二叉树中,叶子结点个数为()A n B n+1 C n-1 D n/2

解析

  1. 设叶子结点数为 n0,度为 1 的结点数为 n1,度为 2 的结点数为 n2,由性质 3 得:n0 = n2 +1 → n2 = n0 -1;
  2. 结点总数:2n = n0 + n1 + n2 = n0 + n1 + (n0 -1) = 2n0 + n1 -1;
  3. 完全二叉树中 n1只能是 0 或 1 ,代入验证:
    • 若 n1=1:2n = 2n0 +1 -1 → 2n=2n0 → n0=n,符合整数要求;
    • 若 n1=0:2n=2n0 -1 → n0=(2n+1)/2,为小数,不符合实际。答案:A
例题 3

一个具有 767 个节点的完全二叉树,其叶子节点个数为()A 383 B 384 C 385 D 386

解析

  1. 由性质 3:n0 = n2 +1,结点总数 n = n0 + n1 + n2 = 2n0 + n1 -1;
  2. 已知 n=767(奇数),因此 2n0 + n1 = 768(偶数);
  3. 2n0 为偶数,根据 "偶数 + 偶数 = 偶数",n1 必须为0(唯一的偶数选择);
  4. 代入得:767 = 2n0 -1 → 2n0=768 → n0=384。答案:B
例题 4

一棵完全二叉树的节点数为 531 个,那么这棵树的高度为()

A 11 B 10 C 8 D 12

解析

  1. 根据性质 4,完全二叉树的高度 k = ⌊log2n⌋+1;
  2. 计算得:29=512,210=1024,因此log2531≈9.05;
  3. 向下取整为 9,高度 k=9+1=10。答案:B

2.5 二叉树的存储结构

二叉树的存储结构分为顺序存储链式存储两种,各有优劣,适用于不同的应用场景:

  • 顺序存储:仅适合完全二叉树,存储效率高,查找父 / 孩子结点快;
  • 链式存储:适合所有二叉树 ,灵活性强,插入 / 删除操作方便,是实际开发中的主流方式
2.5.1 顺序存储
定义

二叉树的顺序存储是指用一段连续的存储单元(通常是数组) 来存储二叉树的结点,结点的存储位置由其在完全二叉树中的编号 决定,核心依据是二叉树的性质 5

存储规则
  1. 完全二叉树 :直接按编号规则 (自上而下、自左至右)将结点存入数组,数组的下标即为结点的编号
  2. 非完全二叉树 :先将其补全为完全二叉树 ,补全的空结点在数组中用特殊值(如 null) 表示,再按编号规则存储。
存储示例
  1. 完全二叉树(根 1、左 2、右 3、2 的左 4、2 的右 5、3 的左 6):数组存储为[1,2,3,4,5,6]
  2. 非完全二叉树(根 1、左 2、2 的左 3):补全为完全二叉树后,数组存储为[1,2,null,3,null,null,null]
优缺点
优点 缺点
存储效率高,无需存储指针 / 引用,仅存储结点数据 仅适合完全二叉树,非完全二叉树会浪费大量存储空间(斜树浪费极致)
通过数组下标可快速定位父结点和孩子结点,无需遍历 插入 / 删除结点困难,需移动大量数组元素,时间复杂度为 O (n)
实现简单,无需处理指针关系 数组大小固定,若二叉树结点数动态变化,易出现数组溢出或空间浪费
应用场景

主要用于存储完全二叉树 ,例如堆(大根堆 / 小根堆) 的实现,充分利用其 "下标定位父 / 孩子结点" 的优势,提升操作效率。

2.5.2 链式存储
定义

二叉树的链式存储是指通过一个一个的结点引用(指针) 连接起来形成的存储结构,每个结点除了数据域 ,还存储指向左孩子右孩子的引用(指针)。

根据是否存储双亲结点的引用 ,链式存储又分为二叉表示法(孩子表示法)三叉表示法(孩子双亲表示法) ,其中孩子表示法是最基础、最常用的方式,本文所有二叉树操作均基于此实现。

方式 1:孩子表示法(二叉表示法,最常用)
结点结构

每个结点包含三个部分:

  1. 数据域:存储结点本身的实际数据(如 int、String 等);
  2. 左孩子引用:存储指向该结点左孩子结点的地址,若没有左孩子,则为 null;
  3. 右孩子引用:存储指向该结点右孩子结点的地址,若没有右孩子,则为 null。
结点定义(Java 代码)
java 复制代码
// 孩子表示法(二叉表示法)
class Node {
    int val; // 数据域,存储结点数据
    Node left; // 左孩子的引用,代表左孩子为根的整棵左子树
    Node right; // 右孩子的引用,代表右孩子为根的整棵右子树

    // 构造方法,初始化数据域,左/右孩子默认为null
    public Node(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}
核心优缺点
优点 缺点
适合所有二叉树,无空间浪费,空结点无需存储 存储开销比顺序存储大,每个结点需额外存储两个引用
插入 / 删除结点方便高效,仅需修改引用指向,无需移动其他结点(时间复杂度 O (1)) 查找双亲结点困难,需遍历整棵二叉树(时间复杂度 O (n))
符合二叉树的递归结构,便于实现递归操作(遍历、统计、查找等) 对内存地址的连续性无要求,易产生内存碎片(Java 中由 GC 处理)
方式 2:孩子双亲表示法(三叉表示法)
结点结构

孩子表示法 的基础上,增加一个双亲引用域,存储指向该结点双亲结点的地址,根结点的双亲引用为 null。

结点定义(Java 代码)
java 复制代码
// 孩子双亲表示法(三叉表示法)
class Node {
    int val; // 数据域,存储结点数据
    Node left; // 左孩子的引用,代表左孩子为根的整棵左子树
    Node right; // 右孩子的引用,代表右孩子为根的整棵右子树
    Node parent; // 当前结点的双亲结点引用,指向父结点

    // 构造方法
    public Node(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
        this.parent = null;
    }
}
核心优缺点
优点 缺点
继承孩子表示法的所有优点,可快速查找双亲结点(时间复杂度 O (1)) 存储开销更大,每个结点需额外存储三个引用
适合需要频繁操作父结点的场景 插入 / 删除结点时,需同时修改孩子引用双亲引用,逻辑更复杂
应用场景

主要用于平衡二叉树(AVL 树)红黑树B 树等需要频繁查找 / 操作父结点的复杂树形结构,后续进阶学习中会详细讲解。

三、二叉树的基本操作(核心应用,递归实现为主)

二叉树的所有基本操作均基于其递归定义实现 ,核心思想是:将对整棵二叉树的操作,拆分为对根结点的操作 + 对左子树的递归操作 + 对右子树的递归操作 ,递归的终止条件为结点为 null(空树无任何操作)。

在学习初期,为降低难度,我们先通过手动快速创建 一棵简单的二叉树来入门,后续再学习基于遍历序列的真正创建方式(面试考点)。

3.1 二叉树的手动创建(入门阶段,非实际开发方式)

3.1.1 前置说明
  1. 手动创建的目的:由于初学者对二叉树的结构掌握还不够深入,手动创建一棵简单的二叉树,可快速进入后续操作的学习,降低学习成本;
  2. 构建的二叉树结构(固定):根结点 1,左孩子 2、右孩子 4;结点 2 的左孩子 3;结点 4 的左孩子 5;结点 5 的右孩子 6(后续所有操作均基于此树);
  3. 注意事项:该方式仅适用于入门学习 ,并非实际开发中的二叉树创建方式;原资料中的代码存在重复变量名(node1) 的笔误,以下代码已修正。
3.1.2 完整代码实现(Java,基于孩子表示法)
java 复制代码
public class BinaryTree {
    // 1. 定义二叉树的结点类(孩子表示法,二叉表示法)
    public static class BTNode {
        BTNode left;   // 左孩子引用
        BTNode right;  // 右孩子引用
        int value;     // 数据域,存储结点数据

        // 结点构造方法:初始化数据域,左/右孩子默认为null
        public BTNode(int value) {
            this.value = value;
            this.left = null;
            this.right = null;
        }
    }

    private BTNode root; // 二叉树的根结点,初始为null

    // 2. 手动创建二叉树,建立结点之间的左/右孩子引用关系
    public void createBinaryTree() {
        // 步骤1:创建所有结点
        BTNode node1 = new BTNode(1);
        BTNode node2 = new BTNode(2);
        BTNode node3 = new BTNode(3);
        BTNode node4 = new BTNode(4);
        BTNode node5 = new BTNode(5);
        BTNode node6 = new BTNode(6);

        // 步骤2:建立引用关系,构建二叉树
        root = node1;        // 根结点为node1(值为1)
        node1.left = node2;  // 根结点1的左孩子为2
        node1.right = node4; // 根结点1的右孩子为4
        node2.left = node3;  // 结点2的左孩子为3
        node4.left = node5;  // 结点4的左孩子为5
        node5.right = node6; // 结点5的右孩子为6
    }

    // 3. 获取根结点,供外部方法(遍历、统计等)调用
    public BTNode getRoot() {
        return root;
    }

    // 测试:验证二叉树创建成功
    public static void main(String[] args) {
        BinaryTree bt = new BinaryTree();
        bt.createBinaryTree();
        BTNode root = bt.getRoot();
        System.out.println("二叉树的根结点值为:" + root.value); // 输出:1
    }
}

3.2 二叉树的遍历:最核心的操作(所有操作的基础,面试必考)

3.2.1 遍历的定义与意义

遍历(Traversal) :指沿着某条搜索路线,依次对树中每个结点做一次且仅做一次访问的操作。

  • 访问 :根据具体需求定义,如打印结点值 、修改结点数据、统计结点个数、查找指定结点等,本文中 "访问" 均指打印结点值
  • 核心意义 :遍历是二叉树最重要、最基础的操作,二叉树的查找、统计、删除、修改等所有操作,均基于遍历实现;
  • 遍历的唯一性 :为保证遍历结果的唯一性,约定用N 表示根结点L 表示左子树R 表示右子树 ,根据访问根结点的时机,将遍历分为前序、中序、后序三种方式。
3.2.2 前序、中序、后序遍历(深度优先,递归实现)

前序、中序、后序遍历均为深度优先遍历 ,其递归实现的代码结构高度相似 ,仅需调整访问根结点的语句位置 ,递归的终止条件均为结点为 null(空树无操作)。

以下所有遍历示例均基于3.1 节手动创建的二叉树(根 1、左 2 右 4;2 左 3;4 左 5;5 右 6),遍历结果可直接验证。

方式 1:前序遍历(NLR:根 → 左 → 右)
遍历规则
  1. 先访问根结点
  2. 递归前序遍历左子树
  3. 最后递归前序遍历右子树核心 :根结点的访问在最前面
遍历步骤(详细)
  1. 访问根结点 1 → 递归前序遍历左子树(根为 2) → 递归前序遍历右子树(根为 4);
  2. 访问根结点 2 → 递归前序遍历左子树(根为 3) → 递归前序遍历右子树(null,直接返回);
  3. 访问根结点 3 → 递归前序遍历左子树(null) → 递归前序遍历右子树(null),遍历结束;
  4. 访问根结点 4 → 递归前序遍历左子树(根为 5) → 递归前序遍历右子树(null,直接返回);
  5. 访问根结点 5 → 递归前序遍历左子树(null) → 递归前序遍历右子树(根为 6);
  6. 访问根结点 6 → 递归前序遍历左子树(null) → 递归前序遍历右子树(null),遍历结束。
遍历结果

1 2 3 4 5 6

代码实现(在 BinaryTree 类中添加方法)
java 复制代码
// 前序遍历对外接口,供外部调用(无需传参)
public void preOrder() {
    preOrder(root);
    System.out.println(); // 遍历结束后换行,美化输出
}

// 前序遍历递归实现,私有方法(仅内部调用,传参为当前结点)
private void preOrder(BTNode node) {
    // 递归终止条件:结点为null,直接返回
    if (node == null) {
        return;
    }
    System.out.print(node.value + " "); // 1. 访问根结点
    preOrder(node.left);                // 2. 递归遍历左子树
    preOrder(node.right);               // 3. 递归遍历右子树
}
方式 2:中序遍历(LNR:左 → 根 → 右)
遍历规则
  1. 递归中序遍历左子树
  2. 访问根结点
  3. 最后递归中序遍历右子树核心 :根结点的访问在中间
遍历结果

3 2 1 5 4 6

代码实现(在 BinaryTree 类中添加方法)
java 复制代码
// 中序遍历对外接口,供外部调用
public void inOrder() {
    inOrder(root);
    System.out.println();
}

// 中序遍历递归实现,私有方法
private void inOrder(BTNode node) {
    if (node == null) {
        return;
    }
    inOrder(node.left);                 // 1. 递归遍历左子树
    System.out.print(node.value + " "); // 2. 访问根结点
    inOrder(node.right);                // 3. 递归遍历右子树
}
方式 3:后序遍历(LRN:左 → 右 → 根)
遍历规则
  1. 递归后序遍历左子树
  2. 递归后序遍历右子树
  3. 最后访问根结点核心 :根结点的访问在最后面
注意事项

原资料中的后序遍历结果为3 1 5 6 4 1,属于笔误 ,以上述规则推导的正确结果为准。

遍历结果

3 2 6 5 4 1

代码实现(在 BinaryTree 类中添加方法)
java 复制代码
// 后序遍历对外接口,供外部调用
public void postOrder() {
    postOrder(root);
    System.out.println();
}

// 后序遍历递归实现,私有方法
private void postOrder(BTNode node) {
    if (node == null) {
        return;
    }
    postOrder(node.left);               // 1. 递归遍历左子树
    postOrder(node.right);              // 2. 递归遍历右子树
    System.out.print(node.value + " "); // 3. 访问根结点
}
前 / 中 / 后序遍历的核心对比

为了更清晰地区分三种遍历方式,从遍历顺序、访问根结点时机、示例结果、代码差异四个维度做对比,便于记忆和实现:

遍历方式 遍历顺序(NLR/LNR/LRN) 访问根结点的时机 示例遍历结果 代码核心差异
前序遍历 NLR(根→左→右) 最前面 1 2 3 4 5 6 先打印,再递归左右子树
中序遍历 LNR(左→根→右) 中间 3 2 1 5 4 6 先递归左子树,再打印,最后递归右子树
后序遍历 LRN(左→右→根) 最后面 3 2 6 5 4 1 先递归左右子树,最后打印
3.2.3 层序遍历(广度优先,借助队列实现)

层序遍历与前 / 中 / 后序遍历不同,属于广度优先遍历 ,无法通过简单的递归实现,需借助队列(Queue) 来实现,是面试的高频考点。

遍历规则

设二叉树的根结点在第 1 层 ,从根结点出发,自上而下、自左至右逐层访问结点:

  1. 先访问第 1 层(根结点);
  2. 再访问第 2 层的所有结点(从左到右);
  3. 接着访问第 3 层的所有结点(从左到右);
  4. 以此类推,直至所有结点访问完毕。
实现思路(核心,熟记)
  1. 初始化一个队列,将根结点入队
  2. 若队列非空,出队队首结点,并访问该结点
  3. 若该结点有左孩子,将左孩子入队;
  4. 若该结点有右孩子,将右孩子入队;
  5. 重复步骤 2-4,直至队列为空。

核心逻辑入队根结点,出队访问,再依次入队左右孩子,保证每层结点按 "左到右" 的顺序访问。

遍历结果(基于手动创建的二叉树)

1 2 4 3 5 6

代码实现(在 BinaryTree 类中添加方法,需导入队列包)
java 复制代码
import java.util.Queue;
import java.util.LinkedList;

// 层序遍历(广度优先),对外接口
public void levelOrder() {
    // 空树,直接返回
    if (root == null) {
        return;
    }
    // 初始化队列,LinkedList是Queue接口的常用实现类
    Queue<BTNode> queue = new LinkedList<>();
    queue.offer(root); // 根结点入队

    // 队列非空则循环遍历
    while (!queue.isEmpty()) {
        BTNode cur = queue.poll(); // 出队队首结点
        System.out.print(cur.value + " "); // 访问当前结点

        // 左孩子非空,入队
        if (cur.left != null) {
            queue.offer(cur.left);
        }
        // 右孩子非空,入队
        if (cur.right != null) {
            queue.offer(cur.right);
        }
    }
    System.out.println();
}
测试所有遍历方法

在 BinaryTree 类的 main 方法中添加测试代码,运行后可直接看到所有遍历结果:

java 复制代码
public static void main(String[] args) {
    BinaryTree bt = new BinaryTree();
    bt.createBinaryTree();

    System.out.print("前序遍历:");
    bt.preOrder(); // 输出:1 2 3 4 5 6

    System.out.print("中序遍历:");
    bt.inOrder();  // 输出:3 2 1 5 4 6

    System.out.print("后序遍历:");
    bt.postOrder();// 输出:3 2 6 5 4 1

    System.out.print("层序遍历:");
    bt.levelOrder();//输出:1 2 4 3 5 6
}
3.2.4 遍历推导经典选择题(附答案 + 深度解析)

遍历推导是二叉树面试的核心选择题题型,重点考查对遍历规则的理解、根结点判定、左右子树拆分等能力,以下结合文档中的 4 道经典例题,进行 step-by-step 解析:

例题 1

某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH ,该完全二叉树的前序序列为()

A: ABDHECFG B: ABCDEFGH C: HDBEAFCG D: HDEBFGCA

解析步骤

  1. 还原完全二叉树结构 :层序序列为 ABCDEFGH,说明结点按 "自上而下、自左至右" 填充,结构如下:
    • 第 1 层:A(根)
    • 第 2 层:B(A 左)、C(A 右)
    • 第 3 层:D(B 左)、E(B 右)、F(C 左)、G(C 右)
    • 第 4 层:H(D 左)(因是完全二叉树,D 的右孩子为空,E、F、G 的孩子暂为空)
  2. 执行前序遍历(根→左→右)
    • 访问 A → 遍历 A 的左子树(以 B 为根)→ 遍历 A 的右子树(以 C 为根)
    • 访问 B → 遍历 B 的左子树(以 D 为根)→ 遍历 B 的右子树(以 E 为根)
    • 访问 D → 遍历 D 的左子树(H)→ D 的右子树(空)→ 访问 H
    • E 的左右子树均为空 → 访问 E
    • 访问 C → 遍历 C 的左子树(F)→ 遍历 C 的右子树(G)
    • F、G 的左右子树均为空 → 访问 F、G
  3. 拼接遍历结果 :A→B→D→H→E→C→F→G,对应选项 A。答案:A
例题 2

二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG. 则二叉树根结点为()A:E B:F C:G D:H

解析步骤

  1. 前序遍历的核心特性 :前序遍历的第一个结点必然是二叉树的根结点(前序规则:根→左→右,根结点最先访问)。
  2. 定位根结点:先序序列的第一个结点为 E,因此根结点为 E。
  3. 验证逻辑 :中序遍历中,根结点 E 将序列分为左子树(HFI)和右子树(JKG),符合中序 "左→根→右" 的规则,进一步确认 E 为根。答案:A
例题 3

设一棵二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为()A: adbce B: decab C: debac D: abcde

解析步骤

  1. 后序遍历定位根结点 :后序遍历规则为 "左→右→根",最后一个结点为根结点,因此根结点为 a。
  2. 中序序列拆分左右子树:中序序列 badce 中,根 a 左侧的 "b" 为左子树,右侧的 "dce" 为右子树。
  3. 递归分析右子树
    • 右子树的后序序列为 "dec"(后序序列 bdeca 去掉左子树 b 和根 a),后序最后一个结点 "c" 为右子树的根。
    • 中序序列中 c 左侧的 "d" 为 c 的左子树,右侧的 "e" 为 c 的右子树。
  4. 还原二叉树结构
    • 根 a → 左孩子 b(叶子结点)→ 右孩子 c
    • c 的左孩子 d(叶子结点)→ c 的右孩子 e(叶子结点)
  5. 执行前序遍历 :a→b→c→d→e,对应选项 D。答案:D
例题 4

某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF ,则按层次输出(同一层从左到右)的序列为()A: FEDCBA B: CBAFED C: DEFCBA D: ABCDEF

解析步骤

  1. 分析遍历序列的一致性条件
    • 后序遍历:左→右→根;中序遍历:左→根→右。
    • 若两者序列相同,说明二叉树无右子树(否则右子树结点会在 "根" 之后,导致序列差异),即二叉树为 "左斜树"(所有结点只有左孩子)。
  2. 还原左斜树结构
    • 后序序列的最后一个结点为根结点,因此根为 F。
    • 中序序列中 F 左侧的 E 为 F 的左孩子,E 左侧的 D 为 E 的左孩子,依次类推,结构为:F←E←D←C←B←A(箭头指向父结点)。
  3. 执行层序遍历 :层序遍历 "自上而下、自左至右",左斜树的层序为根 F→第二层 E→第三层 D→第四层 C→第五层 B→第六层 A,即 FEDCBA。答案:A

3.3 二叉树的常用基本操作(递归实现 + 详细思路)

二叉树的基本操作均基于 "递归分治" 思想:将整棵树的操作拆分为 "根结点操作 + 左子树递归 + 右子树递归",终止条件为 "结点为 null"。以下结合文档中的操作列表,给出完整实现思路和代码(Java),均基于 3.1 节手动创建的二叉树。

3.3.1 获取树中节点的个数(int size(Node root)

核心思路

  • 空树(root==null):结点数为 0;
  • 非空树:结点数 = 1(当前根结点)+ 左子树结点数 + 右子树结点数。

代码实现

java 复制代码
// 对外接口
public int size() {
    return size(root);
}

// 递归实现
private int size(BTNode node) {
    if (node == null) {
        return 0;
    }
    // 1(当前结点)+ 左子树结点数 + 右子树结点数
    return 1 + size(node.left) + size(node.right);
}

测试验证 :手动创建的二叉树有 6 个结点,调用size()返回 6。

3.3.2 获取叶子节点的个数(int getLeafNodeCount(Node root)

核心思路

  • 空树:叶子结点数为 0;
  • 当前结点为叶子(left==null && right==null):返回 1;
  • 非叶子结点:叶子结点数 = 左子树叶子数 + 右子树叶子数。

代码实现

java 复制代码
// 对外接口
public int getLeafNodeCount() {
    return getLeafNodeCount(root);
}

// 递归实现
private int getLeafNodeCount(BTNode node) {
    if (node == null) {
        return 0;
    }
    // 判断当前结点是否为叶子结点
    if (node.left == null && node.right == null) {
        return 1;
    }
    // 左子树叶子数 + 右子树叶子数
    return getLeafNodeCount(node.left) + getLeafNodeCount(node.right);
}

测试验证 :手动创建的二叉树中,叶子结点为 3、6,调用getLeafNodeCount()返回 2。

3.3.3 获取第 K 层节点的个数(int getKLevelNodeCount(Node root,int k)

核心思路

  • 空树或 K<1:返回 0;
  • K=1(根结点所在层):返回 1(仅根结点);
  • K>1:第 K 层结点数 = 左子树第 K-1 层结点数 + 右子树第 K-1 层结点数(递归向下穿透到目标层)。

代码实现

java 复制代码
// 对外接口
public int getKLevelNodeCount(int k) {
    return getKLevelNodeCount(root, k);
}

// 递归实现
private int getKLevelNodeCount(BTNode node, int k) {
    // 边界条件:空树或K无效
    if (node == null || k < 1) {
        return 0;
    }
    // 目标层为根结点层,返回1
    if (k == 1) {
        return 1;
    }
    // 递归查询左、右子树的第K-1层
    return getKLevelNodeCount(node.left, k-1) + getKLevelNodeCount(node.right, k-1);
}

测试验证 :手动创建的二叉树第 3 层结点为 3、5,调用getKLevelNodeCount(3)返回 2。

3.3.4 获取二叉树的高度(int getHeight(Node root)

核心思路

  • 空树:高度为 0;
  • 非空树:高度 = max (左子树高度,右子树高度) + 1(+1 是因为当前根结点所在层)。

代码实现

java 复制代码
// 对外接口
public int getHeight() {
    return getHeight(root);
}

// 递归实现
private int getHeight(BTNode node) {
    if (node == null) {
        return 0;
    }
    // 计算左、右子树的高度
    int leftHeight = getHeight(node.left);
    int rightHeight = getHeight(node.right);
    // 当前树的高度 = 子树最大高度 + 1
    return Math.max(leftHeight, rightHeight) + 1;
}

测试验证 :手动创建的二叉树高度为 4(层 1:1;层 2:2、4;层 3:3、5;层 4:6),调用getHeight()返回 4。

3.3.5 检测值为 value 的元素是否存在(Node find(Node root, int val)

核心思路

  • 空树:返回 null(未找到);
  • 当前结点值 == val:返回当前结点;
  • 未找到:递归查找左子树,找到则返回;左子树未找到则递归查找右子树。

代码实现

java 复制代码
// 对外接口
public BTNode find(int val) {
    return find(root, val);
}

// 递归实现
private BTNode find(BTNode node, int val) {
    if (node == null) {
        return null;
    }
    // 当前结点匹配,返回结点
    if (node.value == val) {
        return node;
    }
    // 递归查找左子树
    BTNode leftFind = find(node.left, val);
    if (leftFind != null) {
        return leftFind;
    }
    // 左子树未找到,递归查找右子树
    return find(node.right, val);
}

测试验证:查找值为 5 的结点,返回 node5;查找值为 10 的结点,返回 null。

3.3.6 判断一棵树是不是完全二叉树(boolean isCompleteTree(Node root)

核心思路:完全二叉树的核心特征是 "按层填充,无中间空缺",借助队列实现:

  1. 按层序遍历顺序,将所有结点(包括 null)入队;
  2. 出队时遇到第一个 null 后,若后续还有非 null 结点,则不是完全二叉树;
  3. 若第一个 null 后全为 null,则是完全二叉树。

代码实现

java 复制代码
public boolean isCompleteTree() {
    if (root == null) {
        return true; // 空树是完全二叉树
    }
    Queue<BTNode> queue = new LinkedList<>();
    queue.offer(root);

    // 第一步:入队所有结点(包括null)
    while (!queue.isEmpty()) {
        BTNode cur = queue.poll();
        if (cur == null) {
            break; // 遇到第一个null,跳出循环
        }
        queue.offer(cur.left);  // 左孩子入队(可能为null)
        queue.offer(cur.right); // 右孩子入队(可能为null)
    }

    // 第二步:检查后续是否有非null结点
    while (!queue.isEmpty()) {
        BTNode cur = queue.poll();
        if (cur != null) {
            return false; // 存在非null结点,不是完全二叉树
        }
    }
    return true;
}

测试验证 :手动创建的二叉树(结构:1→2→3,1→4→5→6)是完全二叉树,调用isCompleteTree()返回 true;若删除结点 3,新增结点 6 的右孩子 7,则不是完全二叉树,返回 false。

四、二叉树经典 OJ 题汇总(实战练习 + 解题思路)

文档中列出的 14 道 OJ 题涵盖了二叉树的核心考点(结构判断、遍历、构造、修改等),以下按 "基础→进阶" 分类,给出每道题的解题思路和关键提示,帮助快速上手实战:

4.1 基础 OJ 题(掌握核心操作即可解决)

1. 检查两颗树是否相同
  • 解题思路 :递归比较两棵树的结点:
    1. 两棵树均为空 → 相同;
    2. 一棵空、一棵非空 → 不同;
    3. 结点值不同 → 不同;
    4. 递归比较左子树和右子树。
  • 关键提示:基于二叉树的递归定义,逐结点比对。
2. 另一颗树的子树
  • 解题思路
    1. 若 A 树为空 → 不是 B 树的子树;
    2. 若当前 A 树与 B 树相同 → 是子树;
    3. 递归检查 A 树的左子树或右子树是否与 B 树相同。
  • 关键提示:复用 "检查两棵树是否相同" 的逻辑,递归遍历主树的所有子树。
3. 翻转二叉树
  • 解题思路 :递归交换每个结点的左右孩子:
    1. 空树 → 直接返回;
    2. 交换当前结点的左、右孩子;
    3. 递归翻转左子树和右子树。
  • 关键提示:后序遍历或前序遍历均可,先交换当前结点的孩子,再递归处理子树。
4. 对称二叉树
  • 解题思路 :递归检查左子树和右子树是否镜像:
    1. 两棵子树均为空 → 对称;
    2. 一棵空、一棵非空 → 不对称;
    3. 结点值不同 → 不对称;
    4. 递归检查左子树的左孩子与右子树的右孩子,左子树的右孩子与右子树的左孩子。
  • 关键提示:核心是 "镜像对比",而非简单的左右子树相同。
5. 二叉树的分层遍历
  • 解题思路 :借助队列实现层序遍历,记录每层的结点数:
    1. 根结点入队;
    2. 循环处理每一层:记录当前队列大小(即该层结点数),出队该层所有结点,入队其左右孩子;
    3. 每层结点单独存储,最终返回二维列表。
  • 关键提示:与基础层序遍历的区别是 "按层分组",需记录每层的结点数量。

4.2 进阶 OJ 题(需结合性质 / 遍历推导)

6. 二叉树的构建及遍历
  • 解题思路
    1. 输入前序 / 中序 / 后序序列(如前序序列);
    2. 按遍历规则递归构建二叉树(前序:根→左→右,先取第一个元素为根,再拆分左右子树序列);
    3. 构建后执行指定遍历(如中序)并输出。
  • 关键提示:掌握 "由遍历序列构建二叉树" 的核心逻辑,是后续复杂构造题的基础。
7. 给定一个二叉树,找到该树中两个指定节点的最近公共祖先
  • 解题思路(递归法)
    1. 若当前结点为空或等于 p/q → 返回当前结点;
    2. 递归查找左子树和右子树;
    3. 左子树返回 null → 公共祖先在右子树;
    4. 右子树返回 null → 公共祖先在左子树;
    5. 左右子树均返回非 null → 当前结点为最近公共祖先。
  • 关键提示:利用递归的 "回溯" 特性,找到第一个同时包含 p 和 q 的结点。
8. 根据一棵树的前序遍历与中序遍历构造二叉树
  • 解题思路
    1. 前序序列的第一个元素为根结点;
    2. 中序序列中找到根结点,拆分左子树和右子树的中序序列;
    3. 根据左、右子树的中序序列长度,拆分前序序列的左、右子树序列;
    4. 递归构建左子树和右子树。
  • 关键提示:核心是 "通过中序序列确定左右子树的范围",前序序列提供根结点位置。
9. 根据一棵树的中序遍历与后序遍历构造二叉树(课后作业)
  • 解题思路
    1. 后序序列的最后一个元素为根结点;
    2. 中序序列中找到根结点,拆分左、右子树的中序序列;
    3. 根据左、右子树的中序序列长度,拆分后序序列的左、右子树序列;
    4. 递归构建左子树和右子树。
  • 关键提示:与前序 + 中序构造的逻辑对称,仅根结点的定位和序列拆分方式不同。
10. 二叉树创建字符串
  • 解题思路 :前序遍历二叉树,按规则拼接字符串:
    1. 空树 → 空字符串;
    2. 仅根结点 → 直接拼接结点值;
    3. 左子树为空、右子树非空 → 根值 + "()" + 右子树字符串;
    4. 其他情况 → 根值 + "(" + 左子树字符串 + ")" + 右子树字符串。
  • 关键提示:处理左子树为空的特殊情况,避免多余的括号。
11-13. 二叉树前序 / 中序 / 后序非递归遍历实现
  • 解题思路(通用:借助栈)
    1. 前序遍历:栈存储结点,先压入右孩子,再压入左孩子(保证左孩子先出栈),出栈时访问结点;
    2. 中序遍历:栈存储结点,先压入所有左孩子,出栈时访问结点,再处理右孩子;
    3. 后序遍历:栈存储结点 + 访问标记,未访问过的结点压入栈(标记为 false),弹出时若未访问则标记为 true、重新压入,同时压入右、左孩子(标记为 false);若已访问则访问结点。
  • 关键提示:非递归遍历的核心是 "用栈模拟递归的调用栈",需注意结点的入栈顺序和访问时机。
14. 判断一颗二叉树是否是平衡二叉树
  • 解题思路(后序遍历优化)
    1. 平衡二叉树定义:左右子树的高度差绝对值≤1,且左右子树均为平衡二叉树;
    2. 后序遍历计算子树高度,同时判断是否平衡:
      • 空树 → 高度 0,平衡;
      • 左子树不平衡 → 直接返回 - 1(标记不平衡);
      • 右子树不平衡 → 直接返回 - 1;
      • 左右子树高度差 > 1 → 返回 - 1;
      • 平衡 → 返回当前树的高度(max (左高,右高)+1)。
  • 关键提示:避免重复计算子树高度,用后序遍历 "一次遍历完成高度计算和平衡判断"。

4.3 练习建议

  1. 先基础后进阶:先完成 "检查两棵树是否相同""翻转二叉树" 等基础题,熟练递归思维后再挑战 "构建二叉树""最近公共祖先" 等进阶题;
  2. 重视非递归遍历:递归遍历易实现,但非递归遍历是面试高频考点,需手动实现三种遍历的非递归版本,理解栈的作用;
  3. 结合性质解题:遇到计算类题目(如叶子结点数、树高),优先想到二叉树的五大性质,简化解题逻辑;
  4. 画图辅助分析:复杂题目(如遍历推导、构建二叉树)建议画图还原树结构,直观理解结点关系。

五、学习总结与备考指南

二叉树的学习核心在于 "理解递归思想" 和 "掌握遍历规则",以下是针对学习和面试的关键总结:

5.1 核心知识点回顾

  1. 树的基础:掌握树的定义、核心术语(结点的度、树的度、叶子结点、层次、高度),树的判定规则(N 个结点 N-1 条边、子树不相交);
  2. 二叉树的核心
    • 定义与特性:度≤2、子树有左右之分;
    • 特殊二叉树:满二叉树(结点数2K−1)、完全二叉树(度为 1 的结点数 0 或 1);
    • 五大性质:重点掌握性质 3(n0=n2+1)和性质 5(完全二叉树编号关系);
  3. 存储结构:链式存储(孩子表示法)是主流,顺序存储仅适用于完全二叉树;
  4. 基本操作:遍历(前 / 中 / 后 / 层序)是基础,结点统计、查找、完全二叉树判断等操作均基于遍历实现;
  5. 实战重点:由遍历序列构建二叉树、非递归遍历、平衡二叉树判断、最近公共祖先等是面试高频题。

5.2 面试备考建议

  1. 概念题:熟记二叉树的性质和特殊二叉树的特征,尤其是完全二叉树的相关考点(如度为 1 的结点数、深度计算);
  2. 推导题:掌握 "由遍历序列推树结构""由树结构推遍历序列",重点练习前序 + 中序、中序 + 后序的组合推导;
  3. 编程题
    • 递归题:熟练掌握递归的终止条件和分治逻辑,避免栈溢出(注意边界条件);
    • 非递归题:重点练习栈在遍历中的应用,理解栈模拟递归的原理;
    • 构造题:掌握由遍历序列构建二叉树的通用逻辑,灵活应对不同序列组合;
  4. 避坑点
    • 完全二叉树与满二叉树的区别,避免混淆;
    • 二叉树的子树有左右之分,次序不能颠倒;
    • 非递归遍历中,结点的入栈顺序和访问时机是关键,避免遍历结果错误。

5.3 拓展学习

二叉树是复杂树形结构的基础,掌握后可进一步学习:

  • 平衡二叉树(AVL 树):解决二叉树倾斜导致的效率问题;
  • 红黑树:一种自平衡的二叉搜索树,广泛应用于 STL、HashMap 等;
  • B 树 / B + 树:数据库索引的核心结构,适用于磁盘存储;
  • 哈夫曼树:数据压缩的经典算法,基于二叉树的带权路径长度最优解。

二叉树的学习没有捷径,唯有 "理解概念 + 多练代码",通过反复练习将递归思维和遍历规则内化为本能,才能在面试中从容应对各类题目。建议收藏本文,结合代码实现和 OJ 练习,逐步攻克二叉树的所有考点!

相关推荐
程序媛徐师姐2 小时前
Java基于微信小程序的球馆预约系统,附源码+文档说明
java·微信小程序·球馆预约系统小程序·jav球馆预约系统小程序·java球馆预约微信小程序·球馆预约微信小程序·java球馆预约系统
En^_^Joy2 小时前
JavaScript入门指南:从零到精通
开发语言·javascript
总斯霖2 小时前
P15445永远在一起!题解(月赛T2)
数据结构·c++·算法·深度优先
于先生吖2 小时前
2026 新版 Java 同城上门家政服务系统源码 完整可运营
java·开发语言
MIXLLRED2 小时前
Python模块详解(一)—— socket 和 threading 模块
开发语言·python·socket·threading
gp3210262 小时前
什么是Spring Boot 应用开发?
java·spring boot·后端
happymaker06262 小时前
JDBC(MySQL)——DAY04(调用存储过程,存储函数)
java·数据库·mysql
csbysj20202 小时前
桥接模式(Bridge Pattern)
开发语言
清空mega2 小时前
第7章:JavaBean、Servlet 与 MVC——从 JSP 页面开发走向规范项目
java·servlet·mvc