有两种通用的遍历树的策略:
- 深度优先搜索(DFS)(栈)
- 在这个策略中,我们采用深度作为优先级,以便从跟开始一直到达某个确定的叶子,然后再返回根到达另一个分支。
- 深度优先搜索策略又可以根据根节点、左孩子和右孩子的相对顺序被细分为先序遍历,中序遍历和后序遍历。
- 广度优先搜索(BFS)(队列)
- 我们按照高度顺序一层一层的访问整棵树,高层次的节点将会比低层次的节点先被访问到。
1
/ \
2 3
/ \ \
4 5 6
- 前序遍历:根节点->左子树->右子树 [1 2 4 5 3 6]
- 中序遍历:左子树->根节点->右子树 [4 2 5 1 3 6]
- 后序遍历:左子树->右子树->根节点 [4 5 2 6 3 1]
- 层序遍历:[1 2 3 4 5 6]
题目出处
- 前序遍历
- 中序遍历
- 后序遍历
- 层序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
1 前序遍历
1.1 递归解法
“自顶向下” 的解决方案
在每个递归层级,首先访问节点来计算一些值,并在递归调用函数时将这些值传递到子节点。
1. return specific value for null node
2. update the answer if needed // answer <-- params
3. left_ans = top_down(root.left, left_params) // left_params <-- root.val, params
4. right_ans = top_down(root.right, right_params) // right_params <-- root.val, params
5. return the answer if needed // answer <-- left_ans, right_ans
- 如果二叉树为空,空操作
- 如果二叉树不为空,访问根节点,前序遍历左子树,前序遍历右子树
class Solution {
public static void preorderTraversalRec(TreeNode root) {
if (root == null) {
return;
}
System.out.print(root.val + " ");
preorderTraversalRec(root.left);
preorderTraversalRec(root.right);
}
}
1.2 迭代解法
用一个辅助栈,从根节点开始,每次迭代弹出当前栈顶元素,并将其孩子节点压入栈中,先压右孩子再压左孩子。
- 时间复杂度:访问每个节点恰好一次,时间复杂度为 O(N)。
- 空间复杂度:取决于树的结构,最坏情况存储整棵树,因此空间复杂度是 O(N)。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (null == root) {
return result;
}
Deque<TreeNode> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
//FILO, push right first,can get left earlier
if (null != node.right) {
stack.push(node.right);
}
if (null != node.left) {
stack.push(node.left);
}
}
return result;
}
}
1.3 相关试题
1)判断两棵二叉树是否相同
递归解法
- 如果两棵二叉树都为空,返回真
- 如果两棵二叉树一棵为空,另一棵不为空,返回假
- 如果两棵二叉树都不为空,如果对应的左子树和右子树都同构返回真,其他返回假
迭代解法
前序遍历,对每个节点进行比较
2)自顶向下的最大深度
class Solution {
private int answer; // don't forget to initialize answer before call maximum_depth
private void maximum_depth(TreeNode root, int depth) {
if (root == null) {
return;
}
if (root.left == null && root.right == null) {
answer = Math.max(answer, depth);
}
maximum_depth(root.left, depth + 1);
maximum_depth(root.right, depth + 1);
}
}
2 中序遍历
2.1 递归解法
中序遍历递归解法
- 如果二叉树为空,空操作。
- 如果二叉树不为空,中序遍历左子树,访问根节点,中序遍历右子树
class Solution {
public static void inorderTraversalRec(TreeNode root) {
if (root == null) {
return;
}
inorderTraversalRec(root.left);
System.out.print(root.val + " ");
inorderTraversalRec(root.right);
}
}
2.2 迭代解法
用栈先把根节点的所有左孩子都添加到栈内,然后输出栈顶元素,再处理栈顶元素的右子树
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (null == root) {
return list;
}
Deque<TreeNode> stack = new ArrayDeque<>();
while (true) {
//1. push all left nodes into stack
while (null != root) {
stack.push(root);
root = root.left;
}
//2. get previous pushed node to continue
//2.1 if empty, means finished
if (stack.isEmpty()) {
break;
}
//2.2 get one
TreeNode node = stack.pop();
//3. handle (left)middle
list.add(node.val);
//4. handle right
root = node.right;
}
return list;
}
}
3 后序遍历
3.1 递归解法
“自底向上” 的解决方案
在每个递归层次上,我们首先对所有子节点递归地调用函数,然后根据返回值和根节点本身的值得到答案。
1. return specific value for null node
2. left_ans = bottom_up(root.left) // call function recursively for left child
3. right_ans = bottom_up(root.right) // call function recursively for right child
4. return answers // answer <-- left_ans, right_ans, root.val
- 如果二叉树为空,空操作
- 如果二叉树不为空,后序遍历左子树,后序遍历右子树,访问根节点
class Solution {
public static void postorderTraversalRec(TreeNode root) {
if (root == null) {
return;
}
postorderTraversalRec(root.left);
postorderTraversalRec(root.right);
System.out.print(root.val + " ");
}
}
3.2 迭代解法
后序遍历跟中序遍历很像,先遍历左边,判断中点时,先判断有没有右节点,有的话要先处理右边。
处理完右边会回到中点,中点还是有右节点,为了不陷入循环,需要记录下右节点是否处理过。
因为每个中点只关心一个右节点,并且这个右节点pop之后下一个pop的一定是对应的中点,所以只需要一个额外变量记录处理过的右节点。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (null == root) {
return result;
}
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode handledRight = null;
while (true) {
//1. push all left nodes into stack
while (null != root) {
stack.push(root);
root = root.left;
}
//2. get previous pushed node to continue
//2.1 if empty, means finished
if (stack.isEmpty()) {
break;
}
//2.2 get one
TreeNode node = stack.pop();
//3.1 if has right, current node is a middle node
if (null != node.right && node.right != handledRight) {
// if right hasn't been handled, push current middle note back to stack
stack.push(node);
// start to handle right
root = node.right;
} else {
//3.2 if doesn't has right, or right has been handled, can handle middle node
result.add(node.val);
handledRight = node;
}
}
return result;
}
}
`
3.3 相关试题
1)自底向上的最大深度
class Solution {
public int maximum_depth(TreeNode root) {
if (root == null) {
return 0; // return 0 for null node
}
int left_depth = maximum_depth(root.left);
int right_depth = maximum_depth(root.right);
return Math.max(left_depth, right_depth) + 1; // return depth of the subtree rooted at root
}
}
4 层序遍历
4.1 递归解法
很少有人会用递归去做 level traversal,基本思想是用一个大的 ArrayList,里面包含了每一层的 ArrayList。大的 ArrayList 的 size 和 level 有关系
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> levels = new ArrayList<>();
if (null == root) {
return levels;
}
dfs(root, 0, levels);
return levels;
}
private static void dfs(TreeNode node, int level, List<List<Integer>> levels) {
// 添加一个新的 ArrayList 表示新的一层
if (level >= levels.size()) {
levels.add(new ArrayList<Integer>());
}
// 把节点添加到当前这层的 ArrayList 里
levels.get(level).add(node.val);
// 递归处理下一层的左子树和右子树
if (null != node.left) {
dfs(node.left, level + 1, levels);
}
if (null != node.right) {
dfs(node.right, level + 1, levels);
}
}
}
4.2 迭代解法
相当于广度优先搜索,使用队列实现。队列初始化,将根节点压入队列。当队列不为空,进行如下操作: 弹出一个节点 ,访问,若左子节点或右子节点不为空,将其压入队列
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> levels = new ArrayList<>();
if (null == root) {
return levels;
}
Deque<TreeNode> queue = new ArrayDeque<>();
queue.addLast(root);
//each loop, handle one level
while (!queue.isEmpty()) {
List<Integer> thisLevel = new ArrayList<>();
//nodes in queue now if from one level
int thisLevelSize = queue.size();
while (thisLevelSize-- > 0) {
//fetch this level's node
TreeNode node = queue.removeFirst();
thisLevel.add(node.val);
//add next level node into queue
if (null != node.left) {
queue.addLast(node.left);
}
if (null != node.right) {
queue.addLast(node.right);
}
}
levels.add(thisLevel);
}
return levels;
}
}
4.3 相关试题
1)自底向上的层次遍历
在层次遍历的基础上,使返回的列表是倒序就行
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
LinkedList<List<Integer>> result = new LinkedList<>();
if (null == root) {
return result;
}
ArrayDeque<TreeNode> queue = new ArrayDeque<>();
int level = 0;
queue.addLast(root);
while (!queue.isEmpty()) {
result.addFirst(new ArrayList<>());
int levelSize = queue.size();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.removeFirst();
result.get(0).add(node.val);
if (null != node.left) {
queue.addLast(node.left);
}
if (null != node.right) {
queue.addLast(node.right);
}
}
levelSize++;
}
return result;
}
}
2)一棵树每层节点的平均数
问题:给定一个非空二叉树, 返回一个由每层节点平均值组成的数组.
思路:层序遍历,计算每层的平均值
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> result = new ArrayList<>();
if (null == root) {
return result;
}
ArrayDeque<TreeNode> queue = new ArrayDeque<>();
queue.addLast(root);
while (!queue.isEmpty()) {
int count = queue.size();
double sum = 0;
for (int i = 0; i < count; ++i) {
TreeNode node = queue.removeFirst();
sum += node.val;
if (null != node.left) {
queue.addLast(node.left);
}
if (null != node.right) {
queue.addLast(node.right);
}
}
result.add(sum / count);
}
return result;
}
}
3)得到左下角的节点
问题:给定一个二叉树,在树的最后一行找到最左边的值。
思路:层序遍历,从右到左,遍历的最后一个节点就是左下角的节点
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while (!queue.isEmpty()) {
root = queue.poll();
if (null != root.right) {
queue.add(root.right);
}
if (null != root.left) {
queue.add(root.left);
}
}
return root.val;
}
}
4)求二叉树中的节点个数
- 递归:
- 如果二叉树为空,节点个数为 0;
- 如果二叉树不为空,二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1
- 迭代:同层序遍历,每次往队列里加节点的时候计数加一
5)二叉树的最大深度
递归解法
- 如果二叉树为空,二叉树的深度为 0
- 如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
迭代解法
层序遍历,每层加一
6)求二叉树第 K 层的节点个数
递归解法
- 如果二叉树为空或者 k < 1 返回 0
- 如果二叉树不为空并且 k== 1,返回 1
- 如果二叉树不为空且 k > 1,返回 root 左子树中 k-1 层的节点个数与 root 右子树 k-1 层节点个数之和
class Solution {
public static int getNodeNumKthLevelRec(TreeNode root, int k) {
if (root == null || k < 1) {
return 0;
}
if (k == 1) {
return 1;
}
int numLeft = getNodeNumKthLevelRec(root.left, k - 1);
int numRight = getNodeNumKthLevelRec(root.right, k - 1);
return numLeft + numRight;
}
}
迭代解法
层序遍历,到第 k 层时计数
7)求二叉树中叶子节点的个数
递归解法
- 若当前节点为空,返回 0
- 当前节点为叶子节点,返回 1
- 否则返回左右子树的叶子节点之和
class Solution {
public int getNodeNumLeafRec(TreeNode root) {
if (root == null) {
return 0;
}
if (root.left == null && root.right == null) {
return 1;
}
return getNodeNumLeafRec(root.left) + getNodeNumLeafRec(root.right);
}
}
迭代解法
层序遍历,如果当前节点没有左右子树,则是叶子节点,计数加一
5 其它
自顶向下
101. 对称二叉树
递归
- 如果一个树的左子树与右子树镜像对称,那么这个树是对称的。
- 如果同时满足下面的条件,两个树互为镜像:
- 它们的两个根结点具有相同的值
- 每个树的右子树都与另一个树的左子树镜像对称
class Solution {
public boolean isSymmetric(TreeNode root) {
return isSymmetric(root, root);
}
private boolean isSymmetric(TreeNode left, TreeNode right) {
if (left == null && right == null) {
return true;
} else if (left == null || right == null) {
return false;
} else if (left.val != right.val) {
return false;
} else {
//每个树的右子树都与另一个树的左子树镜像对称
return isSymmetric(left.left, right.right) && isSymmetric(right.left, left.right);
}
}
}
迭代
引入一个队列,把递归改写成迭代。
- 将应该镜像的两个点连续入队(两个结点的左右子结点按相反的顺序插入队列中)
- 每次提取两个结点并比较它们的值,然后插入两个结点的左右子结点
- 当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束
class Solution {
public boolean isSymmetric(TreeNode root) {
return isSymmetric(root, root);
}
public boolean isSymmetric(TreeNode left, TreeNode right) {
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(left);
queue.offer(right);
while(!queue.isEmpty()){
TreeNode newLeft = queue.poll();
TreeNode newRight = queue.poll();
if(newLeft == null && newRight == null){
continue;
}
if(newLeft == null || newRight == null){
return false;
}
if(newLeft.val != newRight.val){
return false;
}
queue.offer(newLeft.left);
queue.offer(newRight.right);
queue.offer(newLeft.right);
queue.offer(newRight.left);
}
return true;
}
}