
二叉树是计算机科学中最基础且重要的数据结构之一,不仅是许多高级数据结构(如AVL树、红黑树、堆等)的基础,也是面试中频繁考察的知识点。本文将系统性地介绍二叉树的核心概念、特性、操作方式以及常见面试题,帮助读者从零开始构建对二叉树的完整理解。
树是一种非线性的数据结构,由 n(n>=0)个有限节点组成的一个具有层次关系的集合。
它看起来像一个倒挂的树,根在上叶子朝下。
树具有以下特点:

树有多种表示方法:孩子表示法、孩子双亲表示法、孩子兄弟表示法,其中最常用的是孩子兄弟表示法。
本文采用孩子表示法:
class TreeNode {
public char val;
public TreeNode left; // 左孩子
public TreeNode right; // 右孩子
}二叉树是结点的一个有限集合,该集合要么为空要么由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
特点是:
一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树。
也就是说,如果一棵二叉树的层数为 K,且结点总数是 2ᴷ-1 ,则它就是满二叉树。
满二叉树的树形如下:

图中的二叉树层数是3,结点总数为 7 = 2³ - 1,满足要求,这棵树就是满二叉树。
完全二叉树是由满二叉树引出的。
对于深度为 K 的,有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 0 至 n-1 的结点一一对应时,称之为完全二叉树。
即满足节点数按照从上到下从左到右的顺序依次编号,如图:

如果树形是下图,就不是完全二叉树:

注意:满二叉树是一种特殊的完全二叉树。
1. 某一叉树共有399个结点,其中有199个度为2的结点,则该二叉树中的叶子结点数为( )
A 不存在这样的二叉树
B 200
C 198
D 199
答案:B.200 解析:根据性质3,n₀ = n₂ + 1 = 199 + 1 = 200
2. 在具有 2n 个结点的完全二叉树中,叶子结点个数为( )
A n
B n+1
C n-1
D n/2
答案:A.n 解析:
3. 一个具有767个节点的完全二叉树,其叶子节点个数为( )
A 383
B 384
C 385
D 386
答案:B.384 解析:
4. 一棵完全二叉树的节点数为531个,那么这棵树的高度为( )
A 11
B 10
C 8
D 12
答案:B.10 解析:根据性质4,高度k=⌈log₂(531+1)⌉=⌈log₂532⌉。2⁹=512<532<1024=2¹⁰,所以k=10
对于完全二叉树,可以使用数组来表示,且不会浪费空间。
顺序存储使用数组来存储二叉树,特别适用于完全二叉树。
链式存储通过节点之间的引用关系来表示二叉树,常见的表示方式是孩子表示法:
class TreeNode {
public int val; // 数据域
public TreeNode left; // 左孩子的引用
public TreeNode right; // 右孩子的引用
}遍历是二叉树最重要的操作之一,是按照某种规则依次访问二叉树中所有节点的过程。
二叉树常用的遍历方法有四种:
访问顺序:根节点 → 左子树 → 右子树

public void preOrder(TreeNode root) {
if (root == null) return;
System.out.print(root.val + " "); // 访问根节点
preOrder(root.left); // 遍历左子树
preOrder(root.right); // 遍历右子树
}访问顺序:左子树 → 根节点 → 右子树

public void inOrder(TreeNode root) {
if (root == null) return;
inOrder(root.left); // 遍历左子树
System.out.print(root.val + " "); // 访问根节点
inOrder(root.right); // 遍历右子树
}访问顺序:左子树 → 右子树 → 根节点

public void postOrder(TreeNode root) {
if (root == null) return;
postOrder(root.left); // 遍历左子树
postOrder(root.right); // 遍历右子树
System.out.print(root.val + " "); // 访问根节点
}从上到下、从左到右逐层访问节点
实现思路如下:

public void levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
if (root == null) return;
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
System.out.print(cur.val+" ");
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
}这里提供两个思路:
我们实现第二种思路如下:
具体实现如下:
public int size(TreeNode root) {
if (root == null) return 0;
// 递归计算
return size(root.left) + size(root.right) + 1;
}这里同样提供两种思路:
我们这里采用第二种思路(即子问题)来实现:
public int leafSize(TreeNode root) {
if (root == null) return 0;
if (root.left==null && root.right==null) // 叶子节点
return 1;
return leafSize(root.left) + leafSize(root.right);
}我们使用递归的方法来求:
如图:

具体实现如下:
public int KthLevelSize(TreeNode root, int k) {
if (root == null) return 0;
// 当到达目标层时回溯
if (k == 1) return 1;
// 返回左右子树结果之和
return KthLevelSize(root.left,k-1) + KthLevelSize(root.right,k-1);
}二叉树的高度根据概念应该是左右子树高度的最大值再加上1,通过递归得到每一个根的左右子树的高度然后求最大值再加1即可:

具体实现如下:
public int getHeight(TreeNode root) {
// 当节点为空时,回溯
if (root == null) return 0;
// 求出每个根结点左右子树的高度
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
// 返回左右子树最大高度+1
return Math.max(leftHeight,rightHeight) + 1;
}算法实现思路:
具体实现如下:
public TreeNode findVal(TreeNode root, char val) {
if (root == null) return null;
// 若根节点的值是对应值val,就返回根结点
if (root.val == val) return root;
// 若根结点不是,检查左右子树的返回值
TreeNode leftTree = findVal(root.left,val);
if (leftTree != null)
return leftTree;
TreeNode rightTree = findVal(root.right,val);
if (rightTree != null)
return rightTree;
// 若都不是,返回null
return null;
}算法实现思路:

具体实现如下:
public boolean isSame(TreeNode p,TreeNode q) {
// 判断两棵树结构是否相同
if (p!=null && q==null
|| p==null && q!=null)
return false;
// 若结果相同且两棵树根结点都为空,返回true
if (p==null && q==null)
return true;
// 若结构相同且都不为空,判断值是否相同
if (p.val != q.val)
return false;
// 若结构相同值也相同,递归判断左右子树是否相同
return isSame(p.left,q.left) && isSame(p.right,q.right);
}时间复杂度(p树有m个节点,q树有n个节点):O(min(m,n)) 分析:节点个数不同的话结构就不同,同时遍历两棵树,一旦遇到结构不同就直接返回false并结束程序,这取决于哪棵树的节点个数更少,因此复杂度是两棵树节点个数的最小值
算法实现思路:

具体实现如下:
public boolean isSubtree(TreeNode root,TreeNode subRoot) {
// 若root树为空,subRoot树肯定不是root树的一个子树
if (root == null)
return false;
// 若两颗二叉树结构相同值也相同,就返回true
if (isSame(root,subRoot))
return true;
// 若root树的左子树与subRoot相同,返回true
if (isSubtree(root.left,subRoot))
return true;
// 若root树的右子树与subRoot相同,返回true
if (isSubtree(root.right,subRoot))
return true;
// 若根、左右子树都不相同,返回false
return false;
}时间复杂度:O(r*s):root树共有r个节点,subroot树共有s个节点
算法实现思路:

具体实现如下:
public TreeNode invertTree(TreeNode root) {
if (root == null)
return null;
if (root.left==null && root.right==null)
return root;
// 交换左右孩子节点的地址
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
// 依次递归遍历左子树和右子树
invertTree(root.left);
invertTree(root.right);
return root;
}算法实现思路:

具体实现如下:
public boolean isSymmetric(TreeNode root) {
if (root == null)
return true;
return isSymmetricChild(root.left,root.right);
}
public boolean isSymmetricChild(TreeNode leftTree, TreeNode rightTree) {
// 结构不相同
if ((leftTree!=null && rightTree==null)
|| (leftTree==null && rightTree!=null)) {
return false;
}
// 结构相同但是都为空
if (leftTree==null && rightTree==null) {
return true;
}
// 结构相同但值不相同
if (leftTree.val != rightTree.val) {
return false;
}
// 结构相同且值也相同
return isSymmetricChild(leftTree.right,rightTree.left)
&& isSymmetricChild(leftTree.left,rightTree.right);
}平衡二叉树是一种左右子树的高度差不超过1的二叉树

算法实现思路
具体实现如下:
public boolean isBalance(TreeNode root) {
if (root == null)
return true;
// 获取左右子树的高度并求出差值
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
int h = Math.abs(leftHeight - rightHeight);
return (h <= 1) && isBalance(root.left)
&& isBalance(root.right);
}
public int getHeight(TreeNode root) {
if (root == null)
return 0;
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
return Math.max(leftHeight,rightHeight) + 1;
}时间复杂度:O(N²)
分析:重复大量求节点的高度,导致效率减慢 解决方法:在每次求高度时判断左右子树高度的差值绝对值是否满足<2,若满足就返回左右子树最大值+1,否则就返回-1
优化后的算法:
public boolean isBalancedTree(TreeNode root) {
if (root == null)
return true;
if (getHeightOfTree(root) > 0)
return true;
else
return false;
}
public int getHeightOfTree(TreeNode root) {
if (root == null)
return 0;
int leftH = getHeightOfTree(root.left);
// 若左子树的高度为负数,整棵树不可能是平衡二叉树,返回-1
if (leftH < 0)
return -1;
int rightH = getHeightOfTree(root.right);
// 当右子树的高度不为负数且左右子树高度差不超过1,认为是平衡二叉树
if (rightH >= 0 && (Math.abs(leftH-rightH) <= 1))
return Math.max(leftH,rightH) + 1;
else
return -1;
}时间复杂度:O(N)
二叉搜索树:每个左孩子结点都比子树根节点小,右孩子节点都比子树根节点大。
中序遍历得到的结果是有序的。
算法实现思路:

具体实现如下:
TreeNode prev = null;
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null)
return null;
ConvertChildNode(pRootOfTree);
TreeNode head = pRootOfTree;
while(head.left != null) {
head = head.left;
}
return head;
}
public void ConvertChildNode(TreeNode root) {
// 中序遍历
if (root == null)
return;
ConvertChildNode(root.left);
root.left = prev;
if (prev != null)
prev.right = root;
prev = root;
ConvertChildNode(root.right);
}将输入的字符串(前序遍历)转为一棵二叉树,然后中序遍历该二叉树并输出结果
算法实现思路:
具体实现如下:
public static int i = 0;
// 构建二叉树
public static TreeNode createTreeByStr(String str) {
if (str == null)
return null;
TreeNode root = null;
if (str.charAt(i) != '#') {
// 实例化成结点
root = new TreeNode(str.charAt(i++));
// 构建左右子树
root.left = createTreeByStr(str);
root.right = createTreeByStr(str);
} else {
i++;
}
return root;
}
// 中序遍历
public void inOrder(TreeNode root) {
if (root == null) return;
inOrder(root.left); // 遍历左子树
System.out.print(root.val + " "); // 访问根节点
inOrder(root.right); // 遍历右子树
}算法实现思路:
具体实现如下:
public String tree2str(TreeNode root) {
StringBuilder stringBuilder = new StringBuilder();
tree2strChild(root,stringBuilder);
return stringBuilder.toString();
}
public void tree2strChild(TreeNode root, StringBuilder stringBuilder) {
if (root == null)
return;
// 添加根节点的值
stringBuilder.append(root.val);
if (root.left != null) {
stringBuilder.append("(");
tree2strChild(root.left,stringBuilder); // 递归左子树
stringBuilder.append(")");
} else {
if (root.right != null)
stringBuilder.append("()");
else
return;
}
if (root.right != null) {
stringBuilder.append("(");
tree2strChild(root.right,stringBuilder); // 递归右子树
stringBuilder.append(")");
} else {
return;
}
}算法实现思路:

具体实现如下:
public boolean isCompleteTree (TreeNode root) {
if (root == null)
return true;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
// 让cur拿到队头元素
TreeNode cur = queue.poll();
if (cur != null) {
// 队头元素不为空时入队该节点的左右子树
queue.offer(cur.left);
queue.offer(cur.right);
} else {
// 若队头元素为空就结束循环
break;
}
}
// 检查队列中的元素是否都是空结点
while (!queue.isEmpty()) {
if (queue.peek() != null) {
return false;
}
queue.poll();
}
return true;
}算法实现思路:


具体实现如下:
public TreeNode lowestCommonAncestor(TreeNode root,
TreeNode p, TreeNode q) {
// 若根结点是空就返回空
if (root == null)
return null;
// 如果p是根结点或者q是根结点,公共祖先就是根结点
if (p==root || q==root)
return root;
TreeNode leftTree = lowestCommonAncestor(root.left,p,q);
TreeNode rightTree = lowestCommonAncestor(root.right,p,q);
// 若leftTree和rightTree都不为空,最近的公共祖先就是它们的根节点
if (leftTree!=null && rightTree!=null)
return root;
else if (leftTree != null)
return leftTree;
else
return rightTree;
}思路二:
可以改成两个节点的相遇点,使用两个栈,分别存储从根结点到两个指定节点路径上的节点;如果没有找到指定节点就出栈。然后找两个栈的公共节点,即先看哪个栈长,让长的弹出lenA-lenB个元素再两个栈一起弹出元素,当两个栈顶元素相同时,该节点就是公共祖先节点
算法实现步骤: 写一个找到指定节点路径的所有节点的方法(getPath),先把路径节点都存储起来
然后找两个路径的交点即可
public TreeNode lowestCommonAncestor2(TreeNode root,
TreeNode p, TreeNode q) {
if (root == null)
return null;
// 构建两个存储从根节点到指定节点的路径的栈
Stack<TreeNode> stackP = new Stack<>();
Stack<TreeNode> stackQ = new Stack<>();
getPath(root,p,stackP);
getPath(root,q,stackQ);
// 获取两个栈的长度
int sizeP = stackP.size();
int sizeQ = stackQ.size();
if (sizeP > sizeQ) {
int size = sizeP - sizeQ;
while (size-- != 0)
stackP.pop();
} else {
int size = sizeQ - sizeP;
// 让长的栈先弹出差值数个元素
while (size-- != 0)
stackQ.pop();
}
// 找到两个栈的交点
while (!stackP.isEmpty()
&& !stackQ.isEmpty()) {
// 两个栈的栈顶元素相同,该节点就是公共祖先
if (stackP.peek() == stackQ.peek())
return stackP.pop();
else {
// 否则就继续同时弹出元素
stackP.pop();
stackQ.pop();
}
}
// 若找不到就返回null
return null;
}
// 用于构建存储路径的栈的方法
public boolean getPath(TreeNode root, TreeNode node, Stack<TreeNode> stack) {
if (root == null)
return false;
// 若要找到节点是根节点,就直接入栈根节点
stack.push(root);
if (node == root)
return true;
// 递归查找左右子树
boolean ret = getPath(root.left,node,stack);
if (ret)
return true;
ret = getPath(root.right,node,stack);
if (ret)
return true;
// 若没有找到指定节点,说明树中不存在
stack.pop();
return false;
}根据中序遍历和前/后序遍历结果推出二叉树的过程如图:


算法实现思路:
算法实现步骤:
具体实现如下:
public int preIndex;
public TreeNode buildTreeByPreAndInOrder(char[] preorder, char[] inorder) {
return buildTreeChildPAI(preorder,inorder,0,inorder.length-1);
}
public TreeNode buildTreeChildPAI(char[] preorder, char[] inorder,
int inBegin, int inEnd) {
// 这种情况表示该节点没有子树
if (inBegin > inEnd)
return null;
// 先构建根节点
TreeNode root = new TreeNode(preorder[preIndex]);
// 再在中序数组中根据范围找到根节点的下标位置
int rootIndex = findVal(inorder,inBegin,inEnd,preorder[preIndex]);
preIndex++;
// 然后构建左子树和右子树
root.left = buildTreeChildPAI(preorder,inorder,inBegin,rootIndex-1);
root.right = buildTreeChildPAI(preorder,inorder,rootIndex+1,inEnd);
// 当全部构建完成,返回根节点
return root;
}算法实现思路:
算法实现步骤:
具体实现如下:
public int postIndex;
public TreeNode buildTreeByInAndPostOrder(char[] inorder, char[] postorder) {
postIndex = postorder.length-1;
return buildTreeChildIAP(inorder,postorder,0,inorder.length-1);
}
public TreeNode buildTreeChildIAP(char[] inorder, char[] postorder,
int inBegin, int inEnd) {
// 这种情况表示该节点没有子树
if (inBegin > inEnd)
return null;
// 先构建根节点
TreeNode root = new TreeNode(postorder[postIndex]);
// 再在中序数组中根据范围找到根节点的下标位置
int rootIndex = findVal(inorder,inBegin,inEnd,postorder[postIndex]);
postIndex--;
// 然后构建右子树和左子树
root.right = buildTreeChildIAP(inorder,postorder,rootIndex+1,inEnd);
root.left = buildTreeChildIAP(inorder,postorder,inBegin,rootIndex-1);
// 当全部构建完成,返回根节点
return root;
}算法实现思路:
算法实现步骤:

具体实现如下:
public void preOrderNoRe(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur!=null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
System.out.print(cur.val+" ");
cur = cur.left;
}
TreeNode top = stack.pop();
cur = top.right;
}
}public void inOrderNoRe(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur!=null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.pop();
System.out.print(top.val+" ");
cur = top.right;
}
}public void postOrderNoRe(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode prev = null;
while (cur!=null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.peek();
if (top.right==null || top.right==prev) {
System.out.print(top.val+" ");
stack.pop();
prev = top;
} else {
cur = top.right;
}
}
}算法实现思路:
具体实现如下:
public List<List<Character>> levelOrder(TreeNode root) {
List<List<Character>> list = new ArrayList<>();
if (root == null)
return list;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
List<Character> ret = new ArrayList<>();
while (size-- != 0) {
TreeNode cur = queue.poll();
ret.add(cur.val);
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
list.add(ret);
}
return list;
}至此,本文任务已达成,希望读者能够掌握好二叉树的知识并灵活运用 ~
完