二叉树的前序遍历–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);
}
}
中序遍历的思路:
- 将根节点的左节点,根节点的左节点的左节点,…,直到最深的左节点依次压入Stack中,此时栈顶的元素是最左侧的元素,其目的是找到一个最小单位的子树(也就是最左侧的一个节点)。
- 当处理完最小单位的子树时,栈中记录了该节点的上层,返回到上层处理中间节点。
- 如果有右节点,其也要进行中序遍历。
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));
}