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

news/2024/5/20 9:55:56

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://www.niftyadmin.cn/n/547501.html

相关文章

cpython库的作用_Dynamo升级使用 Python3 CPython 调用外部库

安装的revit2021版本&#xff0c;绑定的dynamo版本是2.6的&#xff0c;不能用CPython&#xff0c;不能调用numpy那些外部库&#xff0c;需要升级dynamo到2.7及以上才可以。不过要等revit更新就要revit2022版本了&#xff0c;这里自己替换更新。主要参考 https://blog.csdn.net/…

从头开始学jsp

从头开始学jsp 0.0.1 版权 © 2008 叮咚 老菜鸟叮咚 对文档的任何问题或建议&#xff0c;请给叮咚发邮件或留言。 QQ:475784337 QQ群:51239192 MSN:lingirl6hotmail.com EMAIL:lingirl6hotmail.com 2008-02-28 20:05:06 序言 1. 想用jsp做网站的朋友看过来 2. 预备知识 3. …

一款方便车牌号输入的键盘和文本框

2019独角兽企业重金招聘Python工程师标准>>> Demo点此(Swift):https://pan.baidu.com/s/1qXG6nkK 1.显示自定义文本框,TouchDown代理告诉控制器应 该显示自定义的键盘了. /// 车牌号 文本框 (自定义)let carNumberContentButton XNInputCarNumebrButton();/// 车牌…

dbunit java_java – org.dbunit.dataset.NoSuchColumnException

我运行测试时遇到以下错误&#xff1a;org.dbunit.dataset.NoSuchColumnException: myTable.MYFIELD - (Non-uppercase input column: myfield) in ColumnNameToIndexes cache map. Note that the maps column names are NOT case sensitive.at org.dbunit.dataset.AbstractTab…

微信扫码支付PHP接入总结

微信扫码支付分为两种模式&#xff0c; 模式一比较复杂&#xff0c;需要公众号配置回调地址。 模式二比较简单&#xff0c;只需要在代码中配置回调地址就可以了。 我这次使用的是模式二。 需要配置参数&#xff0c; const APPID xxx; const MCHID xxx; const KEY xxx; const…

Microsoft.Exchange.OMA.PreferencingServerResources.Resources.dll.EN

今天在安裝 exchange 2003 的時候。提示復制錯誤。。Microsoft.Exchange.OMA.PreferencingServerResources.Resources.dll.EN 找不到該文件。 解決方案&#xff1a; 打開 \exchange2003\setup\i386\exchange\oma\Browse\bin\EN 到該目錄下找到Microsoft.Exchange.OMA.…

转移单的装运和收货

正如前文所说的&#xff0c;AX的很多功能与窗体绑定了&#xff0c;需要剥离出来&#xff0c;有一些类提供了供其他代码直接调用的方法&#xff0c;比如PurchFormLetter及SalesFormLetter的Update方法&#xff0c;它们就帮忙处理了Parm*等一系列的表&#xff0c;并自动过账&…