Binary Tree Postorder Traversal — LeetCode

Binary Tree Postorder Traversal — LeetCode原题链接: http://oj.leetcode.com/problems/binary-tree-postorder-traversal/ 跟BinaryTreeInorderTraversal以及BinaryTreePreorderTraversal一样,二叉树的后序遍历我们还是介绍三种方法,第一种是递归,第二种是迭代方法,第三种是用线索二叉树。递归还是那么简单,算法

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定
原题链接: 
http://oj.leetcode.com/problems/binary-tree-postorder-traversal/
 



Binary Tree Inorder Traversal
以及
Binary Tree Preorder Traversal
一样,二叉树的后序遍历我们还是介绍三种方法,第一种是递归,第二种是迭代方法,第三种是用线索二叉树。 递归还是那么简单,算法的时间复杂度是O(n), 而空间复杂度则是递归栈的大小,即O(logn)。代码如下:

public ArrayList<Integer> postorderTraversal(TreeNode root) {
    ArrayList<Integer> res = new ArrayList<Integer>();
    helper(root, res);
    return res;
}
private void helper(TreeNode root, ArrayList<Integer> res)
{
    if(root == null)
        return;
    helper(root.left,res);
    helper(root.right,res);
    res.add(root.val);
}

Jetbrains全家桶1年46,售后保障稳定
接下来是迭代的做法,本质就是用一个栈来模拟递归的过程,但是相比于
Binary Tree Inorder Traversal

Binary Tree Preorder Traversal
,后序遍历的情况就复杂多了。我们需要维护当前遍历的cur指针和前一个遍历的pre指针来追溯当前的情况(注意这里是遍历的指针,并不是真正按后序访问顺序的结点)。具体分为几种情况:


(1)如果pre的左孩子或者右孩子是cur,那么说明遍历在往下走,按访问顺序继续,即如果有左孩子,则是左孩子进栈,否则如果有右孩子,则是右孩子进栈,如果左右孩子都没有,则说明该结点是叶子,可以直接访问并把结点出栈了。


(2)如果反过来,cur的左孩子是pre,则说明已经在回溯往上走了,但是我们知道后序遍历要左右孩子走完才可以访问自己,所以这里如果有右孩子还需要把右孩子进栈,否则说明已经到自己了,可以访问并且出栈了。


(3)如果cur的右孩子是pre,那么说明左右孩子都访问结束了,可以轮到自己了,访问并且出栈即可。


算法时间复杂度也是O(n),空间复杂度是栈的大小O(logn)。实现的代码如下:

public ArrayList<Integer> postorderTraversal(TreeNode root) {
    ArrayList<Integer> res = new ArrayList<Integer>();
    if(root == null)
        return res;
    LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
    stack.push(root);
    TreeNode pre = null;
    while(!stack.isEmpty())
    {
        TreeNode cur = stack.peek();
        if(pre==null || pre.left==cur || pre.right==cur)
        {
            if(cur.left!=null)
            {
                stack.push(cur.left);
            }
            else if(cur.right!=null)
            {
                stack.push(cur.right);
            }
            else
            {
                res.add(cur.val);
                stack.pop();
            }
        }
        else if(cur.left==pre && cur.right!=null)
        {
            stack.push(cur.right);
        }
        else
        {
            res.add(cur.val);
            stack.pop();
        }
        pre = cur;
    }
    return res;
}

上面迭代实现的思路虽然清晰,但是实现起来还是分情况太多,不容易记忆。我后来再看wiki的时候发现有跟Binary Tree Inorder TraversalBinary Tree Preorder Traversal非常类似的解法,容易统一进行记忆,思路可以参考其他两种,区别是最下面在弹栈的时候需要分情况一下:
1)如果当前栈顶元素的右结点存在并且还没访问过(也就是右结点不等于上一个访问结点),那么就把当前结点移到右结点继续循环;
2)如果栈顶元素右结点是空或者已经访问过,那么说明栈顶元素的左右子树都访问完毕,应该访问自己继续回溯了。
下面列举一下代码:

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<Integer>();
    if(root == null)
    {
        return res;
    }
    LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
    TreeNode pre = null;
    while(root != null || !stack.isEmpty())
    {
        if(root!=null)
        {
            stack.push(root);
            root = root.left;
        }
        else
        {
            TreeNode peekNode = stack.peek();
            if(peekNode.right!=null && pre != peekNode.right)
            {
                root = peekNode.right;
            }
            else
            {
                stack.pop();
                res.add(peekNode.val);
                pre = peekNode;
            }
        }
    }
    return res;
}

最后介绍Morris遍历方法,这个方法不需要为每个节点额外分配指针指向其前驱和后继结点,而是利用叶子节点中的右空指针指向中序遍历下的后继节点就可以了,所以优势在于不需要额外空间。不过同样相比于Binary Tree Inorder TraversalBinary Tree Preorder Traversal,后序遍历的情况要复杂得多,因为后序遍历中一直要等到孩子结点访问完才能访问自己,需要一些技巧来维护。

在这里,我们需要创建一个临时的根节点dummy,把它的左孩子设为树的根root。同时还需要一个subroutine来倒序输出一条右孩子路径上的结点。跟迭代法一样我们需要维护cur指针和pre指针来追溯访问的结点。具体步骤是重复以下两步直到结点为空:


1. 如果cur指针的左孩子为空,那么cur设为其右孩子。


2. 否则,在cur的左子树中找到中序遍历下的前驱结点pre(其实就是左子树的最右结点)。接下来分两种子情况:


(1)如果pre没有右孩子,那么将他的右孩子接到cur。将cur更新为它的左孩子。


(2)如果pre的右孩子已经接到cur上了,说明这已经是回溯访问了,可以处理访问右孩子了,倒序输出cur左孩子到pre这条路径上的所有结点,并把pre的右孩子重新设为空(结点已经访问过了,还原现场)。最后将cur更新为cur的右孩子。


空间复杂度同样是O(1),而时间复杂度也还是O(n),倒序输出的过程只是加大了常数系数,并没有影响到时间的量级。如果对这个复杂度结果不是很熟悉的朋友,可以先看看
Binary Tree Inorder Traversal
中的分析,在那个帖子中讲得比较详细。实现的代码如下:

public ArrayList<Integer> postorderTraversal(TreeNode root) {
    ArrayList<Integer> res = new ArrayList<Integer>();
    TreeNode dummy = new TreeNode(0);
    dummy.left = root;
    TreeNode cur = dummy;
    TreeNode pre = null;
    while(cur!=null)
    {
        if (cur.left==null)
        {
            cur = cur.right;
        }
        else
        {
            pre = cur.left;
            while (pre.right!=null && pre.right!=cur)
                pre = pre.right;
            if (pre.right==null)
            {
                pre.right = cur;
                cur = cur.left;
            }
            else
            {
                reverse(cur.left, pre);
                TreeNode temp = pre;
                while (temp != cur.left)
                {
                    res.add(temp.val);
                    temp = temp.right;
                }
                res.add(temp.val);
                reverse(pre, cur.left);
                pre.right = null;
                cur = cur.right;
            }
        }
    } 
    return res;
}
void reverse(TreeNode start, TreeNode end) 
{
    if (start == end)
        return;
    TreeNode pre = start;
    TreeNode cur = start.right;
    TreeNode next;
    while (pre != end)
    {
        next = cur.right;
        cur.right = pre;
        pre = cur;
        cur = next;
    }
}

到目前为止,二叉树的三种遍历的三种方法都介绍过了,后序遍历相比于前面两种,还是要复杂一些,个人觉得面试中可能倾向于靠其他两种遍历,特别是像Morris遍历方法,如果没有准备过很难在面试中写出这种方法的后序遍历,主要还是要有概念,就是知道方法的存在性以及优劣的分析就可以了,不过递归法和迭代法还是需要掌握一下的。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/223421.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • python从字符串中提取数字

    1、使用正则表达式,用法如下:##总结##^匹配字符串的开始。##$匹配字符串的结尾。##\b匹配一个单词的边界。##\d匹配任意数字。##\D匹配任意非数字字符。##x?匹配一个可选的x字符(换言之,它匹配1次或者0次x字符)。##x*匹配0次或者多次x字符。##x+匹配1次或者多次x字符。##x{n,m}匹配…

  • C# 解析 sln 文件

    C# 解析 sln 文件我的项目,编码工具需要检测打开一个工程,获取所有项目。但是发现原来的方法,如果存在文件夹,把项目放在文件夹中,那么是无法获得项目,于是我就找了一个方法去获得sln文件的所有项目。

  • 挖洞经验丨敏感信息泄露+IDOR+密码确认绕过=账户劫持

    挖洞经验丨敏感信息泄露+IDOR+密码确认绕过=账户劫持本文中涉及到的相关漏洞已报送厂商并得到修复,本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担。今天分享的这篇Writeup是作者在HackerOne上某个邀请测试项目的发现,目标网站存在不安全的访问控制措施,可以利用其导致的敏感信息泄露(auth_token)+密码重置限制绕过,以越权(IDOR)方式,实现网站任意账户劫持(Takeover)。整个测试过程是一次最基本…

  • MySQL中变量的定义和变量的赋值使用

    MySQL中变量的定义和变量的赋值使用

  • strip 命令的使用方法

    strip 命令的使用方法

  • flask框架2_flask框架介绍

    flask框架2_flask框架介绍flask框架2文章目录flask框架2一.状态保持1.Session细节二.高级处理1.上下文2.请求勾子3.路由变量3.1绑定动态URL(重点)3.2正则转换器4.werkzerg库结构(了解)三.参数和配置1.Flask()参数2.Flask应用配置四.脚本启动五.模板1.模板变量的基本使用(重点)2.过滤器一.状态保持1.Session细节…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号