二叉树的遍历:前序,中序,后序
(前,中,后,都是以根节点的访问顺序为参照。前序即为:根节点在最前;中序即为:根节点在中间;后序即为:根节点在最后。)
先上非常简单的递归版
// 前序: 根 - 左 - 右
private void dfs(TreeNode root, List<Integer> ans) {
if (root == null) return ;
ans.add(root.val); // 先访问根节点
dfs(root.left, ans); // 递归的访问左子树
dfs(root.right, ans); // 递归的访问右子树
}
// 中序: 左 - 根 - 右
private void dfs(TreeNode root, List<Integer> ans) {
if (root == null) return ;
dfs(root.left, ans); // 递归的访问左子树
ans.add(root.val); // 访问根节点
dfs(root.right, ans); // 递归的访问右子树
}
// 后序: 左 - 右 - 根
private void dfs(TreeNode root, List<Integer> ans) {
if (root == null) return ;
dfs(root.left, ans); // 递归的访问左子树
dfs(root.right, ans); // 递归的访问右子树
ans.add(root.val); // 访问根节点
}
用递归的方式来写,代码非常简单,也很容易理解。递归的精妙就在于此,我们只需要想清楚递归函数的定义,然后从逻辑上完成它,剩下的工作,就全部交给计算机去处理了。递归函数,其实对我们开发者来说,很多时候都像是一个黑盒子,我们其实并不太容易想的明白,它的每一步具体都是怎么执行的。
为了深入理解计算机是如何执行递归方法的,我们还需要掌握二叉树的非递归遍历。也就是,我们需要人为的使用栈,来完成计算机自动帮我们完成的操作。
前序遍历
前序遍历是模拟起来最简单的。
写法一:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
// 先把根节点入栈
if (root != null) stack.push(root);
// 当栈不空时, 循环
while (!stack.isEmpty()) {
TreeNode x = stack.pop(); // 弹出栈顶, 并直接访问
ans.add(x.val);
if (x.right != null) stack.push(x.right); // 右儿子最后访问, 先入栈
if (x.left != null) stack.push(x.left); // 左儿子先访问, 后入栈
}
return ans;
}
}
写法二:(此种风格,可以统一地应用在中序,后序遍历上)
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
// root为当前处理的节点
while (root != null || !stack.isEmpty()) {
while (root != null) {
ans.add(root.val); // 直接访问
stack.push(root); // 入栈做保存
root = root.left; // 更新当前节点为左儿子
}
TreeNode x = stack.pop();
if (x.right != null) root = x.right;
}
return ans;
}
}
中序遍历
中序遍历,由于根节点不是最先访问的,最先访问的是左儿子,所以先一头扎下去,扎到整个左子树的最左侧。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root); // 先把当前节点入栈
root = root.left;
}
// 当这个节点没有左儿子时, 访问这个节点, 这个节点是当前栈顶
TreeNode x = stack.pop();
ans.add(x.val);
if (x.right != null) root = x.right;
}
return ans;
}
}
后序遍历
根是最后访问的,这种模拟起来最困难。通常是先得到逆序,然后翻转。
写法一:(对前序遍历的写法一稍加修改)
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
if (root != null) stack.push(root);
while (!stack.isEmpty()) {
TreeNode x = stack.pop();
ans.add(x.val);
if (x.left != null) stack.push(x.left);
if (x.right != null) stack.push(x.right);
}
// 反转顺序
Collections.reverse(ans);
return ans;
}
}
写法二:(统一风格的写法)
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
ans.add(root.val);
stack.push(root);
root = root.right;
}
TreeNode x = stack.pop();
if (x.left != null) root = x.left;
}
Collections.reverse(ans);
return ans;
}
}
写法三:(双栈)(仔细观察一下其实和写法一是一样的,这种写法是用一个额外的栈来做逆序而已)
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
Deque<TreeNode> temp = new LinkedList<>();
if (root != null) temp.push(root);
while (!temp.isEmpty()) {
TreeNode x = temp.pop();
stack.push(x);
if (x.left != null) temp.push(x.left);
if (x.right != null) temp.push(x.right);
}
while (!stack.isEmpty()) {
TreeNode x = stack.pop();
ans.add(x.val);
}
return ans;
}
}
写法四:不逆序,需要一个额外的变量来存储前一个已访问的节点
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
TreeNode pre = null; // 前一个已经处理过的节点
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
// 先一头扎到最左侧, 此时栈顶是最左侧的节点, 看该节点
TreeNode x = stack.pop();
// 如果该节点不存在右儿子, 或者上一个访问的节点是其右儿子, 则说明该访问此节点了
if (x.right == null || pre == x.right) {
ans.add(x.val);
pre = x;
root = null;
} else {
// 否则, 当前节点还无法访问, 重新入栈, 并处理其右儿子
stack.push(x);
root = x.right;
}
}
return ans;
}
}