Binary Tree Preorder Traversal 前序遍历
访问顺序:根 左 右
栈
复杂度
O(N) 时间 O(N) 系统栈空间
思路
先写一个utility: pushAllLeft(root):把root(包含)一路向左通到底,路过的节点将其访问(加入到result),并压入栈,留作备用;
此时栈顶的元素满足:他的左孩子以及他自己必已经被访问;栈顶元素都是下一个应该访问的子树的根,将其弹出,(不必访问,因为他进栈时已被访问),然后尝试访问它的右孩子;
如果有右孩子,把右孩子一路向左通到底(pushAllLeft),路过的同样,访问,然后压栈;
如果没有右孩子,啥也不干,访问下一个栈顶元素;
注意
访问节点的过程在一路向左的过程中完成,即在utility完成
代码
一路向左(访问,并压栈)的utility:
//push all root's left and left's left ... (including root itself) into the stack and add the to result
public void pushAllLeft(Stack<TreeNode> stack, TreeNode root, List<Integer> result) {
while (root != null) {
stack.push(root);
result.add(root.val);
root = root.left;
}
}
主程序:
public class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
List<Integer> result = new ArrayList<>();
pushAllLeft(stack, root, result);
while (!stack.isEmpty()) {
TreeNode cur = stack.pop();//栈顶必定是已经添加到过结果里并且访问完了左孩子,该访问右孩子了
if (cur.right != null) {
cur = cur.right;
pushAllLeft(stack, cur, result);
}
}
return result;
}
}
Binary Tree Inorder Traversal 中序遍历
访问顺序:左 根 右
栈
复杂度
O(N) 时间 O(N) 系统栈空间
思路
也先写一个utility: pushAllLeft(root):把root(包含)一路向左压入栈,不访问,留作备用;
此时栈顶的元素满足:他的左孩子必已经被访问,自己还未访问;栈顶元素都是下一个应该访问的子树的根,将其弹出,访问之,然后尝试访问它的右孩子:
如果有右孩子,把右孩子一路向左通到底(pushAllLeft),同样也是不访问;
如果没有右孩子,啥也不干,访问下一个栈顶元素;
注意
访问节点的动作在从栈中弹出的一瞬间进行,不是在utility完成
代码
一路向左(不访问,压栈)的utility:
//push all root's left and left's left ... (including root itself) into the stack without adding to result
public void pushAllLeft(Stack<TreeNode> stack, TreeNode root, List<Integer> result) {
while (root != null) {
stack.push(root);
root = root.left;
}
}
主程序:
public class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
pushAllLeft(stack, root, result);
while (!stack.isEmpty()) {
TreeNode cur = stack.pop();//栈顶还没有访问,弹出来的时候访问,访问完了访问他的右孩子
result.add(cur.val);
if (cur.right != null) {
pushAllLeft(stack, cur.right, result);
}
}
return result;
}
}
Binary Tree Postorder Traversal 后序遍历
访问顺序:左 右 根
栈
复杂度
O(N) 时间 O(N) 系统栈空间
思路
比较诡异,随便画个树得到后序遍历的序列后观察,发现只要把前序遍历反着来,就可以得到后序遍历,这是一个规律,通过自己走一遍树的后序遍历得来的,至于理论依据什么的并没有。
先写一个utility: pushAllRight(root):把root(包含)一路向右通到底,路过的节点将其访问(插入到result的开头),并压入栈,留作备用;
此时栈顶的元素满足:他的右孩子以及他自己必已经被访问;栈顶元素都是下一个应该访问的子树的根,将其弹出,(不必访问,因为他进栈时已被访问),然后尝试访问它的左孩子;
如果有左孩子,把左孩子一路向右通到底(pushAllLeft),路过的同样,访问,然后压栈;
如果没有左孩子,啥也不干,访问下一个栈顶元素;
注意
访问节点的过程在一路向右的过程中完成,即在utility完成,和前序遍历如出一辙
因为在访问的时候要把当前元素插入到(addFirst)result的开头,所以用LinkedList(实现是双向链表)时间上会比较好,注意用他的addFirst方法的话不能声明成List<Integer>,直接声明成LinkedList就好了,我也忘了addFirst是哪个接口里的了。。
代码
一路向右(访问,并压栈)的utility:
//push all root's right and right's right ... (including root itself) into the stack and add the to result
public void pushAllRight(Stack<TreeNode> stack, TreeNode root, LinkedList<Integer> result) {
while (root != null) {
stack.push(root);
result.addFirst(Integer.valueOf(root.val));
root = root.right;
}
}
主程序:
public class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
LinkedList<Integer> result = new LinkedList<Integer>();//注意这里要用linkedlist声明
pushAllRight(stack, root, result);
while (!stack.isEmpty()) {
TreeNode cur = stack.pop();
if (cur.left != null) {
cur = cur.left;
pushAllRight(stack, cur, result);
}
}
return result;
}
}
总结
三个遍历都用一个栈可以搞定,写一个一路向左/右的utility,然后不断访问栈顶即可。
前序和中序的区别只有一点,那就是何时访问节点:
在前序遍历中,一路向左的过程,即压栈的过程中就访问了节点;一路向左完了之后,栈内的元素自然都满足左孩子和自己都被访问过的property,接下来只要不断地弹栈顶,找右孩子,把右孩子一路向左,如此循环直到栈为空。
在中序遍历中,一路向左的过程(也即压栈的过程)并不访问节点;一路向左完了之后,站内的元素满足左子树被访问完了,自己还没有被访问的property,接下来只要不断地弹栈顶,访问之,找他的右孩子,把右孩子一路向左,如此循环直到栈为空。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。