二叉树DFS遍历有三种:pre-order, in-order, post-order。遍历的方法有recursion和iteration两种。
1. pre-order前序遍历
递归:这三种遍历的递归做法都非常简单。
public class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> preOrder(TreeNode root) {
// Write your solution here
if(root == null) return res;
res.add(root.key);
preOrder(root.left);
preOrder(root.right);
return res;
}
}
非递归:主要是用stack来做。这种更好!
为什么用非递归更好?
如果用递归,我们是要用内存中的call-stack来做,它是一个size-limited memory area,如果调用很多很多次,容易stackOverFlow exception
但是用iteration做,使用数据结构中的stack,是在heap上,这样,在stack上消耗的空间是trivial的,我们不用change the total space consumption,而是把space consumption
从stack移到了heap上
思路是:root是stack里的top元素,一旦被遍历过,就『打印』出来,打印之后,要遍历左子树,此时要求右子树留在stack,
所以,在压入的时候,先右后左
public class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> preOrder(TreeNode root) {
// iterative
if(root == null) return res;
Deque<TreeNode> stack = new LinkedList<>();
stack.push(root);
while(!stack.isEmpty()) {
TreeNode cur = stack.pop();
res.add(cur.key);
if(cur.right != null) {
stack.push(cur.right);
}
if(cur.left != null) {
stack.push(cur.left);
}
}
return res;
}
}
2. in-order中序遍历:
递归:
/**
* public class TreeNode {
* public int key;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int key) {
* this.key = key;
* }
* }
*/
public class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> inOrder(TreeNode root) {
// Write your solution here
if(root == null) return res;
inOrder(root.left);
res.add(root.key);
inOrder(root.right);
return res;
}
}
非递归:
和preorder不一样的地方在于,在遍历完左子树之前不能把stack中的root扔掉
使用helper node来记录visiting node和subtree
helper != null: 遍历左子树,并且把helper push进stack
helper == null:说明左子树走完了,这时root是在stack的顶端,打印top,helper = top.right
循环终点:helper == null &&
(当helper == null时, 要先pop出helper,打印他的value,如果它有右子树,还要把它的右子树赋给helper(因为下一轮要打印的是它的右子树,而不是它的parent))
/**
* public class TreeNode {
* public int key;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int key) {
* this.key = key;
* }
* }
*/
public class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> inOrder(TreeNode root) {
//iterative
if(root == null) return res;
Deque<TreeNode> stack = new LinkedList<>();
TreeNode helper;
helper = root;
while(helper != null || !stack.isEmpty()) {
if(helper != null) {
stack.push(helper);
helper = helper.left;
}else {
helper = stack.pop();
res.add(helper.key);
helper = helper.right;
}
}
return res;
}
}
注:之前面试的时候,考了这道题,在我说出iteration解法之后,面试官要求我用空间复杂度为O(1)的非递归情况做。当时卡住了,但是后来做出来了,有空再实现代码。
3. postorder后序遍历
递归:
/**
* public class TreeNode {
* public int key;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int key) {
* this.key = key;
* }
* }
*/
public class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> postOrder(TreeNode root) {
// Write your solution here
if(root == null) return res;
postOrder(root.left);
postOrder(root.right);
res.add(root.key);
return res;
}
}
非递归:
第一种方法:
主要思路是用了另一个stack来辅助
例如: 5
/ \
2 8
/ \
1 3
按照Postorder打出来是这样的:1,3,2,8,5(left, right, root)
把它reverse一下:5,8,2,3,1(root, right, left)
是不是很像pre-order了呢!
所以我们可以借鉴pre-order的方式,只不过left和right push进stack的顺序要变一下
缺点:neet to store everything in memory before we can get the whole post order traversal sequence
/**
* public class TreeNode {
* public int key;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int key) {
* this.key = key;
* }
* }
*/
public class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> postOrder(TreeNode root) {
// Write your solution here
if(root == null) return res;
Deque<TreeNode> stack = new LinkedList<>();
Deque<TreeNode> temp = new LinkedList<>();
temp.push(root);
while(!temp.isEmpty()) {
TreeNode cur = temp.pop();
stack.push(cur);
if(cur.left != null) {
temp.push(cur.left);
}
if(cur.right != null) {
temp.push(cur.right);
}
}
while(!stack.isEmpty()) {
res.add(stack.pop().key);
}
return res;
}
}
第二种方法:
这种方法很重要,因为它是最接近recursion在STACK中的运行机制的
要注意的就是direction!
设置一个prev指针,来判断我们接下来要向哪里走
root = stack.top
如果prev == null, 往下(left优先)
如果prev是cur的parent,往下(left优先) 此时有个tricky的判断:cur == prev.left || cur == prev.right
如果prev == cur.left,证明left subtree已经遍历完,我们要往右边走
如果prev == cur.right,说明right subtree遍历完,往上走(stack.pop)
/**
* public class TreeNode {
* public int key;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int key) {
* this.key = key;
* }
* }
*/
public class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> postOrder(TreeNode root) {
// Write your solution here
//iterative method 2
if(root == null) {
return res;
}
Deque<TreeNode> stack = new LinkedList<>();
TreeNode cur;
TreeNode prev = null;
stack.push(root);
while(!stack.isEmpty()) {
cur = stack.peek();
if(prev == null || cur == prev.left || cur == prev.right) {//如果prev==null或者cur是prev的孩子
//go down, left first
if(cur.left != null) {
stack.push(cur.left);
}else if(cur.right != null) {
stack.push(cur.right);
}else {//left == null && right == null, 走到底了
res.add(cur.key);
stack.pop();
}
}else if(prev == cur.left) {//左子树走完了,走右边
if(cur.right != null) {
stack.push(cur.right);
}else {//如果cur.right == null,直接打印stack顶
res.add(cur.key);
stack.pop();
}
}else {//prev == cur.right,go up
res.add(cur.key);
stack.pop();
}
prev = cur;
}
return res;
}
}