一、二叉树的遍历
遍历是二叉树上最重要的操作之一,是二叉树上进行其它运算的基础。
遍历二叉树的时候,如果没有进行某种约定,每个人都按照自己的方式遍历,得出的结果就比较混乱,如果按照某种规则进行约定,则每个人对于同一棵树的遍历结果肯定是相同的。
如果N代表根节点,L代表根节点的左子树,R代表根节点的右子树,则根据遍历根节点的先后次序有以下遍历方式:
- NLR(前序遍历):访问根节点、根的左子树、根的右子树。
- LNR(中序遍历):根的左子树,访问根节点,根的右子树。
- LRN(后序遍历):根的左子树,根的右子树,访问根节点。
二、二叉树的基本操作
2.1 常用接口
// 前序遍历
void preOrderTraversal(Node root);
// 中序遍历
void inOrderTraversal(Node root);
// 后序遍历
void postOrderTraversal(Node root);
// 遍历思路-求结点个数
static int size = 0;
void getSize1(Node root);
// 子问题思路-求结点个数
int getSize2(Node root);
// 遍历思路-求叶子结点个数
static int leafSize = 0;
void getLeafSize1(Node root);
// 子问题思路-求叶子结点个数
int getLeafSize2(Node root);
// 子问题思路-求第 k 层结点个数
int getKLevelSize(Node root);
// 获取二叉树的高度
int getHeight(Node root);
// 查找 val 所在结点,没有找到返回 null
// 按照 根 -> 左子树 -> 右子树的顺序进行查找
// 一旦找到,立即返回,不需要继续在其他位置查找
Node find(Node root, int val);
2.2 实现代码
完整代码
import java.util.ArrayList;
import java.util.List;
class Node{
public char val;
public Node left;
public Node right;
public Node(char val) {
this.val = val;
}
}
public class TestBinaryTree {
//使用穷举的方式创建一棵二叉树
public Node createTree() {
Node A = new Node('A');
Node B = new Node('B');
Node C = new Node('C');
Node D = new Node('D');
Node E = new Node('E');
Node F = new Node('F');
Node G = new Node('G');
A.left = B;
A.right = C;
B.left = D;
B.right = E;
C.left = F;
C.right = G;
return A;
}
//前序遍历
void preOrderTraversal(Node root) {
if (root == null) return;
System.out.print(root.val + " ");
preOrderTraversal(root.left);
preOrderTraversal(root.right);
}
//前序遍历,使用返回list
public List<Character> preOrderTraversal1(Node root) {
List<Character> cList = new LinkedList<>();
if (root == null) return cList;
cList.add(root.val);
List<Character> leftList = preOrderTraversal1(root.left);
cList.addAll(leftList);
List<Character> rightList = preOrderTraversal1(root.right);
cList.addAll(rightList);
return cList;
}
//中序遍历
void inOrderTraversal(Node root) {
if (root == null) return;
inOrderTraversal(root.left);
System.out.print(root.val + " ");
inOrderTraversal(root.right);
}
//后序遍历
void postOrderTraversal(Node root) {
if (root == null) return;
postOrderTraversal(root.left);
postOrderTraversal(root.right);
System.out.print(root.val + " ");
}
//求结点个数 -遍历思路
//把每个结点都遍历一次,size一次++
static int size = 0;
void getSize1(Node root) {
if (root == null) return;
size++;
getSize1(root.left);
getSize1(root.right);
}
//求结点个数 -子问题思路
//只判断目前该结点root是否有值,左右子树交给递归解决。
int getSize2(Node root) {
if (root == null) return 0;
int val = getSize2(root.left) + getSize2(root.right) + 1;
return val;
}
//求叶子结点个数 -遍历思路
//没有左右子树的结点是叶子结点,遍历每一个结点,统计叶子结点
static int leafSize = 0;
void getLeafSize1(Node root) {
if (root == null) return;
if (root.left == null && root.right == null) leafSize++;
getLeafSize1(root.left);
getLeafSize1(root.right);
}
//求叶子结点个数 -子问题思路
//只判断该结点是否是叶子,其它结点交给递归判断。
int getLeafSize2(Node root) {
if (root == null) return 0;
if (root.left == null && root.right == null) return 1;
int val = getLeafSize2(root.left) + getLeafSize2(root.right);
return val;
}
//求第K层节点个数
//子问题思路 -使用递归计算左右子树的节点,然后相加。
int getKLevelSize(Node root, int k) {
if (root == null) {
throw new RuntimeException("k大于二叉树层数 或 根节点为null");
}
if (k == 1) return 1;
int val = getKLevelSize(root.left,k-1) + getKLevelSize(root.right, k-1);
return val;
}
//求二叉树的高度
//子问题思路 -通过递归求左右子树高度,最后相比 取高者+1.
//每次遍历都是通过return里面的+1,对子树高度相加。
//最后左右子树遍历完,二者对比,取高者+1.
int getHeight(Node root) {
if (root == null) return 0;
int leftTree = getHeight(root.left);
int rightTree = getHeight(root.right);
return leftTree > rightTree ? (leftTree+1) : (rightTree+1);
}
//在root中找val值
//子问题解决思路 - 只判断该节点是否是val,其它节点交给递归解决。
//注意最后所有条件不符合,需要返回null
Node find(Node root, char val) {
if (root == null) return null;
if (root.val == val) return root;
Node ret = find(root.left, val);
if (ret != null) return ret;
ret = find(root.right, val);
if (ret != null) return ret;
return null;
}
}
测试类代码
public class Test01 {
public static void main(String[] args) {
TestBinaryTree tbt = new TestBinaryTree();
Node root = tbt.createTree();
tbt.preOrderTraversal(root);
System.out.println();
tbt.inOrderTraversal(root);
System.out.println();
tbt.postOrderTraversal(root);
System.out.println();
System.out.println("--------结点--------");
tbt.getSize1(root);
System.out.println(tbt.size);
System.out.println(tbt.getSize2(root));
System.out.println("--------叶子--------");
tbt.getLeafSize1(root);
System.out.println(tbt.leafSize);
System.out.println(tbt.getLeafSize2(root));
System.out.println("--------第k层结点个数--------");
int kLevelSize = tbt.getKLevelSize1(root, 4);
System.out.println(kLevelSize);
System.out.println("--------求树的高度--------");
System.out.println(tbt.getHeight(root));
System.out.println("--------找到val值--------");
try {
System.out.println(tbt.findVal(root, '1').val);
}catch (NullPointerException e) {
System.out.println("不存在这个字符");
e.printStackTrace();
}
}
}
三、基础面试题
3.1 二叉树的前序遍历
前序遍历是根左右,我们只需要把根输出,左右子树的数据交给递归解决即可。
//前序遍历
void preOrderTraversal(Node root) {
if (root == null) return;
System.out.print(root.val + " ");
preOrderTraversal(root.left);
preOrderTraversal(root.right);
}
3.2 二叉树的中序遍历
中序遍历是左根右,我们只需要把根输出,左右子树的数据交给递归解决即可。
//中序遍历
void inOrderTraversal(Node root) {
if (root == null) return;
inOrderTraversal(root.left);
System.out.print(root.val + " ");
inOrderTraversal(root.right);
}
3.3 二叉树的后序遍历
后序遍历是左右根,我们只需要把根输出,左右子树的数据交给递归解决即可。
//后序遍历
void postOrderTraversal(Node root) {
if (root == null) return;
postOrderTraversal(root.left);
postOrderTraversal(root.right);
System.out.print(root.val + " ");
}
3.4 检查两颗树是否相同
使用子问题思路解决.
判断根节点是否都为空;亦或是一个为空,一个不为空;节点的val是否相同。
//判断两棵树是否相同 - 使用子问题思路解决
//判断根节点:
// 1.是否都是null;值是否相同;一个为空 一个不为空;(结构比较,值比较)
// 2.结构相同,值相同。
//其它节点交给递归解决。
public boolean isSameTree(Node x, Node y) {
if ((x == null && y != null) || (x != null && y == null)) {
return false;
}
if (x == null && y == null) {
return true;
}
if (x.val != y.val) {
return false;
}
return isSameTree(x.left, y.left) && isSameTree(x.right, y.right);
}
3.5 另一颗树的子树
判断是否是 root 这棵树的结构的一部分即可。
直接判断 subRoot 和 root 这两棵树的结构是否相同;不同就判断 root 的子树和 subRoot 是否相同。
//判断sub是否是root的子树。
//判断root是否null,subRoot是否和root相同
//不同就递归判断下面的结构,是否和subRoot相同
public boolean isSubtree(Node root, Node subRoot) {
if (root == null) return false;
if (isSameTree(root, subRoot)) return true;
if (isSubtree(root.left, subRoot)) return true;
if (isSubtree(root.right, subRoot)) return true;
return false;
}
3.6 二叉树最大深度
通过递归求左右子树高度,最后相比 取高者+1.
每次遍历都是通过return里面的+1,对子树高度相加。最后左右子树遍历完,二者对比,取高者+1.
//判断二叉树的最大深度
public int maxDepth(Node root) {
if (root == null) return 0;
int leftTree = maxDepth(root.left);
int rightTree = maxDepth(root.right);
return leftTree > rightTree ? (leftTree+1) : (rightTree+1);
}
3.7 判断一颗二叉树是否是平衡二叉树
解法一:
//判断该二叉树是否是平衡二叉树
//判断根节点的左右子树是否平衡,算出左右高度相减绝对值小于等于1
//接着继续计算左右子树的左右子树。
public boolean isBalanced1(Node root) {
if (root == null) return true;
int leftHeight = maxDepth(root.left);
int rightHeight = maxDepth(root.right);
return Math.abs(leftHeight-rightHeight)<=1
&& isBalanced1(root.left) && isBalanced1(root.right);
}
解法二:
优化了每个节点求深度的过程。
在遍历二叉树结点的时候,顺便判断二叉树是否平衡,就不需要全部结点都遍历。
//判断该二叉树是否是平衡二叉树 - O(N)解法
//上一个解法,每个节点都需要去求深度,导致开销过大。
//O(N)解法,能遍历节点的中途,判断这部分二叉树是否平衡,不需要全部节点都遍历。
public int maxDepth2(Node root) {
if (root == null) return 0;
//递归判断左右子树的高度。
int leftHeight = maxDepth2(root.left);
int rightHeight = maxDepth2(root.right);
//高度不可能是负数,负数表示不平衡
if (leftHeight >= 0 && rightHeight >= 0 &&
//条件成立,说明是平衡的结点
Math.abs(rightHeight - leftHeight) <= 0) {
//返回结点的最大左右子节点,+1加上自己这个结点。
return Math.max(rightHeight, leftHeight) + 1;
}else {
return -1;
}
}
public boolean isBalanced2(Node root) {
return maxDepth2(root) >= 0;
}
3.8 对称二叉树
判断二叉树是否左右对称,有五种情况针对树的结构和数值。
只需要把情况都罗列进去,使用 if 进行判断即可。
其它的结点交给递归处理。
//对称二叉树
public boolean isSymmetricChild(Node leftTree, Node rightTree) {
//判断左右子树结构是否相同
if (leftTree == null && rightTree != null ||
leftTree != null && rightTree == null) {
return false;
}
//判断左右子树是否为 null
if (leftTree == null && rightTree == null) {
return true;
}
//判断左右子树数值是否相同
if (leftTree.val != rightTree.val) {
return false;
}
//左右对称,那么递归解决左树和右树是否对称。
return isSymmetricChild(leftTree.left, rightTree.right) &&
isSymmetricChild(leftTree.right, rightTree.left);
}
//针对不同情况:传递根节点 还是根的左右树
public boolean isSymmetric(Node root) {
if (root == null) return false;
return isSymmetricChild(root.left, root.right);
}