Thoughts on Binary Tree Iterative Traversal

Iterative Traversal本质是把稍后要处理的Node放到stack上,然后依逻辑不断地pop和push,直到stack为空。每次iteration,都从stack上pop出一个node,然后根据其状态采取不同的处理。理论上,每次对node的访问,可以有三种状态:from parent node, from left child and from right child。再往深入想,状态的数量其实是由print的顺序决定的。Node之所以被放入stack,是因为要在将来某时刻print,换句话说,一旦该node完成了print,它就不会再次被访问。

Pre-Order Traversal

第一次访问node,就立刻print,此后不会有第二次访问该node的可能性。所以对于pre-order,pop出来的node只有一种状态,不需要额外的flag来指示状态。

public void preOrder(TreeNode root) {
	if (root == null) {
		return;
	}
	
	Deque<TreeNode> stack = new ArrayDeque();
	stack.offerFirst(root);
	
	while (!stack.isEmpty()) {
		TreeNode node = stack.pollFirst();
		System.out.println(node.key);
		if (node.right != null) {
			stack.offerFirst(node.right);
		}
		if (node.left != null) {
			stack.offerFirst(node.left);
		}
	}
}

In-Order Traversal

Node在第二次访问时才会被打印,因此有两个状态:from parent node and from left child。所以,用一个boolean就可以区分两种状态。需要注意的是第二次访问时才从stack中pop,所以在进入循环时选择peek。

public void inOrder(TreeNode root) {
	if (root == null) {
		return;
	}
	
	Deque<TreeNode> stack = new ArrayDeque<>();
	stack.offerFirst(root);
	boolean isFirstAccess = true;
	
	while (!stack.isEmpty()) {
		TreeNode curr = stack.peekFirst();
		if (isFirstAccess) {
			if (curr.left != null) {				
				stack.offerFirst(curr.left);
			} else {
				isFirstAccess = false;
			}
		} else {
			System.out.println(curr.key);
			stack.pollFirst();
			if (curr.right != null) {
				stack.offerFirst(curr.right);
				isFirstAccess = true;
			}
		}
	}
}

老师给的解法其实很有技巧性。helper为null时表示从from left child,反之则表示from parent node。我认为这种解法思路还是不太一样,这个逻辑不是stack驱动的,而是helper驱动的。可以认为helper始终是下一个从未access的node,当其为null时,就必须向上pop,直到找到右子树不为空的node。循环开始时,可以不把root放入stack。

public void inOrder(TreeNode root) {
	if (root == null) {
		return;
	}
	
	Deque<TreeNode> stack = new ArrayDeque<>();
	TreeNode helper = root;
	
	while (helper != null || !stack.isEmpty()) {
		if (helper != null) {
			stack.offerFirst(helper);
			helper = helper.left;
		} else {
			TreeNode node = stack.pollFirst();
			System.out.println(node.key);
			helper = node.right;
		}
	}
}

Post-Order Traversal

Node在第三次访问时才会打印,因此需要三种状态,所以boolean不好用了。另外,对于in-order,loop每次循环的状态其实都是可以由上一次的状态计算决定的,第二次访问某节点必然是from left child。但对于post-order,状态from parent node并不能推倒出下一个状态是from left child还是from right child。因此,用一个enum来表示状态是不行的。于是引入prev,这样就能很容易的确定当前状态了。

public void postOrder(TreeNode root) {
	if (root == null) {
		return;
	}
	
	Deque<TreeNode> stack = new ArrayDeque<>();
	stack.offerFirst(root);
	TreeNode prev = null;
	while (!stack.isEmpty()) {
		TreeNode curr = stack.peekFirst();
		if (prev == null || prev.left == curr || prev.right == curr) {
			if (curr.left != null) {
				stack.offerFirst(curr.left);
			} else if (curr.right != null) {
				stack.offerFirst(curr.right);
			} else {
				stack.pollFirst();
				System.out.println(curr.key);
			}
		} else if (prev == curr.left) {
			if (curr.right !== null) {
				stack.offerFirst(curr.right);
			} else {
				stack.pollFirst();
				System.out.println(curr.key);
			}
		} else {
				stack.pollFirst();
				System.out.println(curr.key);
		}
		prev = curr;
	}
}

Leave a comment