二叉树的三种遍历方式(递归和非递归)以及模拟计算机系统栈的遍历方式

二叉树的前序遍历–LeetCode144

递归:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
import java.util.List;
import java.util.ArrayList;
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        preorder(root, list);
        return list;
    }
    private void preorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return ;
        }
        list.add(root.val);
        preorder(root.left, list);
        preorder(root.right, list);
    }
}

迭代:

import java.util.List;
import java.util.ArrayList;
import java.util.Stack;
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.empty()) {
            TreeNode delNode = stack.pop();
            list.add(delNode.val);
            if (delNode.right != null) {
                stack.push(delNode.right);
            }
            if (delNode.left != null) {
                stack.push(delNode.left);
            }
        }
        return list;
    }
}

二叉树的中序遍历–LeetCode94

递归:

import java.util.List;
import java.util.ArrayList;
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        inorder(root, list);
        return list;
    }
    private void inorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return ;
        }
        inorder(root.left, list);
        list.add(root.val);
        inorder(root.right, list);
    }
}

中序遍历的思路:

  1. 将根节点的左节点,根节点的左节点的左节点,…,直到最深的左节点依次压入Stack中,此时栈顶的元素是最左侧的元素,其目的是找到一个最小单位的子树(也就是最左侧的一个节点)。
  2. 当处理完最小单位的子树时,栈中记录了该节点的上层,返回到上层处理中间节点。
  3. 如果有右节点,其也要进行中序遍历。
import java.util.List;
import java.util.ArrayList;
import java.util.Stack;
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if (root == null) {
            return list;
        }
        TreeNode curr = root;
        Stack<TreeNode> stack = new Stack<>();
        while (!stack.empty() || curr != null) {
            // 将curr以及左节点,左节点的左节点,。。。依次压入栈中
            while (curr != null) {
                stack.push(curr);
                curr = curr.left;
            }
            //循环结束,curr指向最左边节点的left,即为null
            //第一次循环结束后栈顶是最左边的节点

            TreeNode node = stack.pop();
            list.add(node.val);
            if (node.right != null) {
                curr = node.right;//如果右节点不为空,则即将对右子树进行中序遍历
            }
        }
        return list;
    }
}

二叉树的后序遍历–LeetCode145

递归:

import java.util.List;
import java.util.ArrayList;
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        postorder(root, list);
        return list;
    }
    private void postorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return ;
        }
        postorder(root.left, list);
        postorder(root.right, list);
        list.add(root.val);
    }
}

使用栈的思路:
当遍历完某个根节点的左子树,回到根节点的时候,对于中序遍历和先序遍历可以把当前根节点从栈里弹出,然后转到右子树。举个例子:

     1
    / \
   2   3
  / \
 4   5

当遍历完 2,4,5 的时候,回到 1 之后我们就可以把 1 弹出,然后通过 1 到达右子树继续遍历。

对于后序遍历,当我们到达 1 的时候并不能立刻把 1 弹出,因为遍历完右子树,我们还需要将这个根节点加入到 list 中。所以我们就需要判断是从左子树到的根节点,还是右子树到的根节点。如果是从左子树到的根节点,此时应该转到右子树。如果是从右子树到的根节点,那么就可以把当前节点弹出,并且加入到 list 中。

参考题解:windliang的题解

方案一:用prev指针保存上一次访问的节点,如果当前节点的右节点和上一次遍历的节点相同,那就表明当前节点是从右节点过来的,就可以放心将当前节点加入list中。

import java.util.List;
import java.util.ArrayList;
import java.util.Stack;
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode curr = root;
        TreeNode prev = null;
        while (!stack.empty() || curr != null) {
            if (curr != null) {
                stack.push(curr);
                curr = curr.left;
            }else {
                TreeNode peek = stack.peek();
                // 判断是否进入到右子树
                if (peek.right != null && peek.right != prev) {
                    // 首先要保证右子树不为空,其次当前位置不是从右子树来的
                    // 满足上面的条件才能进入右子树
                    curr = peek.right;
                }else {
                    list.add(peek.val);
                    prev = peek;
                    stack.pop();
                }
            }
        }
        return list;
    }
}

方案二:只需要把每个节点 push 两次,然后判断当前 pop 节点和栈顶节点是否相同。

  • 相同的话,就意味着是从左子树到的根节点。
  • 不同的话,就意味着是从右子树到的根节点,此时就可以把节点加入到 list 中。

举例解释:对于后序遍历遇到根节点的时候不能直接 pop,只有左子树和右子树都访问完了才能添加到list中。对于下面的二叉树,根节点1首先往栈中push两次,然后栈顶的1被弹出,发现与栈顶的节点1相同,说明这个节点是第一次访问,此时不能添加到list中,必须先将右节点和左节点入栈,此时栈中的情况是1,3,3,2,2(栈顶),2和3的出栈也是按照上面的逻辑,最终栈只剩下了1,此时表示子树都访问完了就可以把1加入list中了。

  1
 / \
2   3
import java.util.List;
import java.util.ArrayList;
import java.util.Stack;
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);
        stack.push(root);
        while (!stack.empty()) {
            TreeNode curr = stack.pop();
            if (curr == null) {
                continue;
            }
            if (!stack.empty() && curr == stack.peek()) {
                stack.push(curr.right);
                stack.push(curr.right);
                stack.push(curr.left);
                stack.push(curr.left);
            }else {
                list.add(curr.val);
            }
        }
        return list;
    }
}

模拟系统栈调用来实现二叉树遍历

模拟计算机的系统栈来对一个节点的访问分为三个指令:

  • print:打印当前节点
  • go L:去到左孩子
  • go R:去到右孩子

图示如下:
在这里插入图片描述

思想和图片来源:bobo老师的慕课网LeetCode课程。

前序遍历如下:

import java.util.List;
import java.util.ArrayList;
import java.util.Stack;

class Command {
    String s;// 表示指令,主要有两种:go print
    TreeNode node;// 表示指令作用的节点
    public Command(String s, TreeNode node) {
        this.s = s;
        this.node = node;
    }
}

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        Stack<Command> stack = new Stack<>();
        stack.push(new Command("go", root));//表示的指令是去根节点
        while (!stack.empty()) {
            Command command = stack.pop();
            if (command.s == "print") {
                res.add(command.node.val);
            }else {// 指令是go
                if (command.node.right != null) {
                    stack.push(new Command("go", command.node.right));
                }
                if (command.node.left != null) {
                    stack.push(new Command("go", command.node.left));
                }
                stack.push(new Command("print", command.node));
            }
        }
        return res;
    }
}

这种方式的一大好处是将前序、中序和后序遍历在写法上统一起来了,中序和后序只是在上述代码的基础上print指令的位置不同。如下:
中序遍历:

if (command.node.right != null) {
    stack.push(new Command("go", command.node.right));
}
stack.push(new Command("print", command.node));
if (command.node.left != null) {
    stack.push(new Command("go", command.node.left));
}

后序遍历:

stack.push(new Command("print", command.node));
if (command.node.right != null) {
    stack.push(new Command("go", command.node.right));
}
if (command.node.left != null) {
    stack.push(new Command("go", command.node.left));
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值