[Leetcode] Binary Tree Traversal 二叉树三种非递归遍历

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,接下来只要不断地弹栈顶,访问之,找他的右孩子,把右孩子一路向左,如此循环直到栈为空。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值