二叉树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 && stack is empty
(当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; } }