一、创建二叉树
在进行二叉树的操作之前我们要先创建一棵二叉树,根据上一篇文章已经说过,我将采用孩子表示法来创建二叉树,如下代码所演示
java
public class BinaryTree {
// 孩子表示法的节点类
static class Node {
int val; // 数据域
Node left; // 左孩子
Node right; // 右孩子
// 构造方法:给节点赋值
Node(int val) {
this.val = val;
}
}
public static void main(String[] args) {
// 创建根节点
Node root = new Node(1);
// 创建左右子树
root.left = new Node(2);
root.right = new Node(3);
root.left.left = new Node(4);
}
}
二、二叉树的遍历
首先我们来说第一个操作同时也是最简单的操作------二叉树的遍历,所谓的遍历是沿着某条搜索路线,依次对树中每个结点做一次且仅一次的访问,是二叉树进行其他运算的基础。分别有四种遍历方式为:前序遍历、中序遍历、后序遍历、层序遍历。我将依次讲解每一种遍历,并给出实现。
这里放一张二叉树的图片,方便下面讲解。

2.1 前序遍历
什么是前序遍历?其实就是先访问根结点,再依次访问左孩子和右孩子, 我们用输出来说就是每访问了一个结点我们就输出一个节点,把根结点和左孩子结点都输出了后,再来输出右孩子。比如我们对上面的二叉树进行前序遍历的结果就是:A,B,D,E,C,F,G。把A先输出之后把A的左边走完了再来输出右边,下面的树同理。那么用代码来实现的话我们可以想到怎么做?先A在A的左在B的左,这样子递归下去,自然就是使用递归。递归思路非常简单:
1.如果当前节点为 null,直接返回
2.先访问当前节点(打印)
3.递归遍历左子树
4.递归遍历右子树
代码如下所演示
java
public class BinaryTree {
// 孩子表示法的节点类
static class Node {
int val;
Node left;
Node right;
Node(int val) {
this.val = val;
}
}
// 前序遍历:根 -> 左 -> 右
public static void preOrder(Node root) {
// 递归出口:节点为空就返回
if (root == null) {
return;
}
// 1. 先访问根节点
System.out.print(root.val + " ");
// 2. 递归遍历左子树
preOrder(root.left);
// 3. 递归遍历右子树
preOrder(root.right);
}
public static void main(String[] args) {
// 构建一棵二叉树
Node root = new Node(1);
root.left = new Node(2);
root.right = new Node(3);
root.left.left = new Node(4);
// 前序遍历
System.out.println("前序遍历结果:");
preOrder(root);
}
}
2.2 中序遍历
中序遍历就是先输出左孩子结点再输出根结点最后再来输出右孩子结点。以我们上面的二叉树为例的话中序遍历一遍后的输出结果是:D B E A F C G。顺序就是左-根-右。实现和前序遍历是相似的都是采用递归来实现,只需要在上面的代码进行一点点修改就可以了
java
public class BinaryTree {
// 孩子表示法的节点类
static class Node {
int val;
Node left;
Node right;
Node(int val) {
this.val = val;
}
}
// 中序遍历:左 -> 根 -> 右
public static void inOrder(Node root) {
// 递归出口:节点为空就返回
if (root == null) {
return;
}
// 1. 先递归遍历左子树
inOrder(root.left);
// 2. 访问根节点
System.out.print(root.val + " ");
// 3. 递归遍历右子树
inOrder(root.right);
}
public static void main(String[] args) {
// 构建一棵二叉树
Node root = new Node(1);
root.left = new Node(2);
root.right = new Node(3);
root.left.left = new Node(4);
// 中序遍历
System.out.println("中序遍历结果:");
inOrder(root);
}
}
2.3 后序遍历
后序遍历其实就是先左孩子结点再右孩子结点最后才是根结点,以上面的二叉树为例子的话后序遍历的结果:D E B F G C A。代码思路同样是递归,也只需要在上面代码做一点小修改就可以了。
java
public class BinaryTree {
// 孩子表示法的节点类
static class Node {
int val;
Node left;
Node right;
Node(int val) {
this.val = val;
}
}
// 后序遍历:左 -> 右 -> 根
public static void postOrder(Node root) {
// 递归出口:节点为空就返回
if (root == null) {
return;
}
// 1. 先递归遍历左子树
postOrder(root.left);
// 2. 递归遍历右子树
postOrder(root.right);
// 3. 访问根节点
System.out.print(root.val + " ");
}
public static void main(String[] args) {
// 构建一棵二叉树
Node root = new Node(1);
root.left = new Node(2);
root.right = new Node(3);
root.left.left = new Node(4);
// 后序遍历
System.out.println("后序遍历结果:");
postOrder(root);
}
}
以上便是三种最基础的遍历,层序遍历相较于前面三种,难度会大一些算是进阶的一种遍历方式了。
2.4 层序遍历
顾名思义层序遍历就是一种每一层一层的去遍历,以上面的二叉树为例层序遍历的输出结果:A B C D E F G,这样去遍历的,正因如此它不能简单的去进行递归实现,我们最常使用的就是利用队列来实现,大致的思路:
1.根节点入队
2.队列不为空时,取出队首节点并访问
3.把该节点的左孩子、右孩子依次入队
4.重复直到队列为空
下面是具体实现的代码
java
import java.util.LinkedList;
import java.util.Queue;
public class BinaryTree {
// 孩子表示法的节点类
static class Node {
int val; // 数据域
Node left; // 左孩子
Node right; // 右孩子
// 构造方法:给节点赋值
Node(int val) {
this.val = val;
}
}
public static void levelOrder(Node root) {
if (root == null) {
return;
}
// 用队列存储节点
Queue<Node> queue = new LinkedList<>();
// 根节点先入队
queue.offer(root);
while (!queue.isEmpty()) {
// 取出队首节点
Node node = queue.poll();
// 访问节点
System.out.print(node.val + " ");
// 左孩子入队
if (node.left != null) {
queue.offer(node.left);
}
// 右孩子入队
if (node.right != null) {
queue.offer(node.right);
}
}
}
// 主函数:创建树 + 测试遍历
public static void main(String[] args) {
// 手动创建一棵二叉树
Node root = new Node(1);
root.left = new Node(2);
root.right = new Node(3);
root.left.left = new Node(4);
System.out.print("层序遍历:");
levelOrder(root);
}
}
以上的输出结果是
java
层序遍历: 1 2 3 4
关于层序遍历是必须要借助队列实现的,层序遍历是 "一层一层" 访问,天然符合队列先进先出的特点,不能用栈,也不适合直接递归。
必须要判断头结点是不是空的,如果 root == null 直接 return,否则队列会报空指针异常。
入队一定要先左孩子再右孩子这样才能保证同一层节点从左到右遍历。如果写反了,遍历顺序会变成从右向左。
每次只处理当前出队的一个节点不要一次性把整层处理完(除非你要按层打印),基础层序遍历只需要依次出队、访问、再入子节点即可。并且层序遍历是不依赖递归的,递归复杂且不直观,所以一般直接使用队列+迭代即可。
三、二叉树的基本操作
二叉树的这些操作难度其实都不高,包括:
获取结点个数:
其实就是每一个结点加起来,那么其实就是左数加上右树最后再加上根结点就可以了,那么我们递归就可以把左右都走一遍算出各自的结点数,那么递归的返回条件就是当结点为空的时候返回0
代码实现如下
java
static int size(Node root) {
if (root == null) return 0;
return size(root.left) + size(root.right) + 1;
}
获取叶子结点个数
首先叶子结点是左右都为空的结点,那么递归的时候是不是就可以判断一棵树他的左右都为空的时候就返回1,结点为空就返回0,这样子加起来是不是就是叶子结点的个数了,下面是代码实现
java
int getLeafNodeCount(Node root) {
if (root == null) {
return 0;
}
if (root.left == null && root.right == null) {
return 1;
}
return getLeafNodeCount(root.left) + getLeafNodeCount(root.right);
}
获取第k层结点个数
要算第 k 层有多少节点看根节点的左子树第 k−1 层有多少节点,看根节点的右子树第 k−1 层有多少节点,把两边加起来,就是整棵树第 k 层的节点总数。把大问题拆成两棵子树的小问题,层数每次减 1。递归出口就是节点 == null没有节点,返回 0,k == 1说明已经走到目标层,当前节点存在,返回 1。下面是代码实现
java
int getKLevelNodeCount(Node root, int k) {
if (root == null || k < 1) {
return 0;
}
if (k == 1) {
return 1;
}
return getKLevelNodeCount(root.left, k - 1) + getKLevelNodeCount(root.right, k - 1);
}
获取二叉树的高度
二叉树的高度就是左右树最高的那一颗再加上根结点的第一层就好了,依次递归左右最后判断谁更高再加上根结点那一层就可以了,结点为空就返回0。下面是代码实现
java
int getHeight(Node root) {
if (root == null) {
return 0;
}
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
return Math.max(leftHeight, rightHeight) + 1;
}
检测val值是否存在
这个我们其实就是将val值和每一个结点的val值进行比较就可以了,如果是的话就直接返回这棵树,不是的话就依次递归左树和右树,要注意如果左树已经找到的话就不用再去找右树了。下面是代码实现
java
Node find(Node root, int val) {
if (root == null) {
return null;
}
if (root.val == val) {
return root;
}
Node leftNode = find(root.left, val);
if (leftNode != null) {
return leftNode;
}
return find(root.right, val);
}
判断一棵树是不是完全二叉树
先说一下完全二叉树的特点:前面的节点都是满的,只允许最后一层右边缺节点,一旦遇到 null,后面就不能再出现非 null 节点。所以思路就很明显了用层序遍历(队列)遍历所有节点,遇到第一个 null 之后,剩下所有节点必须全是 null。如果还出现非 null 节点,就不是完全二叉树,所以也是要用队列的。
根节点入队列循环出队节点:如果节点不为空:左孩子入队右孩子入队如果节点为空:标记:已经遇到空节点了继续遍历队列剩下的节点遍历结束前:若在标记之后,又出队了非空节点 → 不是完全二叉树,若后面全是 null → 是完全二叉树。下面是代码实现
java
// 判断一棵二叉树是否是完全二叉树
// 核心思路:层序遍历,遇到第一个 null 后,后面不能再出现非 null 节点
boolean isCompleteTree(Node root) {
// 空树属于完全二叉树
if (root == null) {
return true;
}
// 用队列实现层序遍历
Queue<Node> queue = new LinkedList<>();
// 根节点入队
queue.offer(root);
// 标记:是否已经遇到过空节点(null)
boolean isNull = false;
// 层序遍历
while (!queue.isEmpty()) {
// 取出队首节点
Node cur = queue.poll();
if (cur == null) {
// 当前节点为空,标记已经遇到空节点
isNull = true;
} else {
// 当前节点不为空
// 如果之前已经遇到过空节点,说明不是完全二叉树
if (isNull) {
return false;
}
// 把左孩子和右孩子依次入队(包括 null)
queue.offer(cur.left);
queue.offer(cur.right);
}
}
// 遍历完成,没有发现不符合条件的情况,是完全二叉树
return true;
}
四、总结
本文我们完整学习了二叉树的创建、四大遍历以及高频基本操作,从基础到进阶层层递进,是二叉树入门必须掌握的核心内容。
我们首先使用孩子表示法定义了二叉树节点,并手动构建了一棵用于测试的二叉树,为后续所有操作打下基础。
接着重点讲解了二叉树的四种遍历方式:递归实现的前序、中序、后序遍历,以及借助队列实现的层序遍历,它们是二叉树所有运算的基础,也是面试和刷题的高频考点。
最后我们实现了二叉树最常用的六大基本操作:求总结点个数、求叶子节点个数、求第 k 层节点个数、求二叉树高度、查找指定值节点、判断一棵树是否为完全二叉树。这些方法大多采用递归思想实现,逻辑清晰、易于理解,而判断完全二叉树则延续了层序遍历的队列思路,进一步巩固了层序遍历的用法。