二叉树的遍历方式
二叉树主要有两种遍历方式:
1.深度优先遍历:先往深走,遇到叶子节点再往回走。 2.广度优先遍历:一层一层的去遍历。
深度优先遍历: 前序遍历(递归法,迭代法) 中序遍历(递归法,迭代法) 后序遍历(递归法,迭代法) 广度优先遍历: 层次遍历(迭代法)
本文将对前中后序遍历进行详解。
前中后序指的就是中间节点的位置。看如下中间节点的顺序,就可以发现,中间节点的顺序就是所谓的遍历方式: 前序遍历:中左右 中序遍历:左中右 后序遍历:左右中
而二叉树的遍历,主要可以用到递归和迭代两种方法。
这里给出前中后序的LeetCode题目:
二叉树的前序遍历 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3] 输出:[1,2,3] 示例 2:
输入:root = [] 输出:[] 示例 3:
输入:root = [1] 输出:[1] 示例 4:
输入:root = [1,2] 输出:[1,2] 示例 5:
输入:root = [1,null,2] 输出:[1,2]
提示:
树中节点数目在范围 [0, 100] 内 -100 <= Node.val <= 100
二叉树的中序遍历 定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3] 输出:[1,3,2] 示例 2:
输入:root = [] 输出:[] 示例 3:
输入:root = [1] 输出:[1]
提示:
树中节点数目在范围 [0, 100] 内 -100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
二叉树的后序遍历 给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。
示例 1:
输入:root = [1,null,2,3] 输出:[3,2,1] 示例 2:
输入:root = [] 输出:[] 示例 3:
输入:root = [1] 输出:[1]
提示:
树中节点的数目在范围 [0, 100] 内 -100 <= Node.val <= 100
进阶:递归算法很简单,你可以通过迭代算法完成吗?
二叉树的递归法遍历
首先我们介绍递归法遍历,我们在使用递归法进行遍历二叉树的时候需要确定三个条件: 1.确定递归函数的参数和返回值。 2.确定终止条件。 3.确定单层递归的逻辑。 而对于递归法,前中后序的代码逻辑非常相似,只需要调整顺序即可,这里贴出递归法遍历的Java代码。
前序遍历:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) {return list;}
preOrder(root, list);
return list;
}
public void preOrder(TreeNode root, List<Integer> list) {
if (root == null) {return;}
list.add(root.val);
preOrder(root.left, list);
preOrder(root.right, list);
}
}
中序遍历:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) {return list;}
inOrder(root, list);
return list;
}
public void inOrder(TreeNode root, List<Integer> list) {
if (root == null) {return;}
inOrder(root.left, list);
list.add(root.val);
inOrder(root.right, list);
}
}
后序遍历:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) {return list;}
postOrder(root, list);
return list;
}
public void postOrder(TreeNode root, List<Integer> list) {
if (root == null) {return;}
postOrder(root.left, list);
postOrder(root.right, list);
list.add(root.val);
}
}
二叉树的迭代遍历
二叉树的迭代遍历要比递归遍历稍微复杂一些,我们需要使用到栈(先进后出)。且普通的迭代遍历,对于前中后序遍历的逻辑有一定的不同。
前序遍历: 我们先来看前序遍历, 前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。 首先将根节点放入栈中,然后将其出栈,再将右孩子和左孩子分别压入栈中。 这样出栈的顺序就和前序遍历的顺序一致了。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) {return list;}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
list.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return list;
}
}
中序遍历: 使用迭代法进行中序遍历的逻辑就和前序遍历不太一样了。 因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。 那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。 那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。 即定义一个cur指针,当cur指针不为null时,将它指向的节点压入栈中,然后让它指向该节点的左孩子。也就是先将根节点的所有左孩子入栈。当cur为null时说明上一个节点没有左孩子,那就将该节点出栈并加入到result中,再让cur指向该节点的右孩子。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) {return list;}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
}else {
cur = stack.pop();
list.add(cur.val);
cur = cur.right;
}
}
return list;
}
}
后序遍历: 使用迭代法进行后序遍历的思路和前序遍历比较相似。 先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) {return list;}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
list.add(node.val);
if (node.left != null) {
stack.push(node.left);
}
if (node.right != null) {
stack.push(node.right);
}
}
Collections.reverse(list);
return list;
}
}
二叉树的统一迭代法
针对三种遍历方式,使用迭代法是可以写出统一风格的代码。 将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。 如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。 将访问的节点直接加入到栈中,但如果是处理的节点则后面放入一个空节点, 这样只有空节点弹出的时候,才将下一个节点放进结果集。
前序遍历: 压入栈的顺序:右-左-中,要处理节点为中。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) {return list;}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.peek();
if (node != null) {
stack.pop();
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
stack.push(node);
stack.push(null);
}else {
stack.pop();
node = stack.pop();
list.add(node.val);
}
}
return list;
}
}
中序遍历: 压入栈的顺序:右-中-左,要处理节点为中。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) {return list;}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.peek();
if (node != null) {
stack.pop();
if (node.right != null) {
stack.push(node.right);
}
stack.push(node);
stack.push(null);
if (node.left != null) {
stack.push(node.left);
}
}
else {
stack.pop();
node = stack.pop();
list.add(node.val);
}
}
return list;
}
}
后序遍历: 压入栈的顺序:中-右-左,要处理节点为中。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) {return list;}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.peek();
if (node != null) {
stack.pop();
stack.push(node);
stack.push(null);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
else {
stack.pop();
node = stack.pop();
list.add(node.val);
}
}
return list;
}
}
可以看到,这种统一的迭代方法只需要修改入栈的顺序即可,逻辑一致。
|