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遍历方法,如果没有准备过很难在面试中写出这种方法的后序遍历,主要还是要有概念,就是知道方法的存在性以及优劣的分析就可以了,不过递归法和迭代法还是需要掌握一下的。

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • mysql8.0.26安装及配置超详细教程(ps怎么下载安装详细步骤图)

    文章目录:官网下载MySQL的安装包添加并配置my.ini文件配置系统变量并初始化MySQL安装并启动MySQLNavicat连接MySQL并修其密码安装过程中常见问题及其解决方法官网下载MySQL的安装包下载链接如下:MySQL8.0.20版本其他版本:MySQL8.0.16版本MySQL8.0.20版本压缩包解压后如下图所示:添加并配置my.ini文件在原解压根…

    2022年4月13日
    52
  • 关于iPhone尺寸与分辨率[通俗易懂]

    浅谈不同型号iPhone的尺寸与不同的分辨率首先谈谈编者对分辨率这个概念的认知,分辨率与清晰度挂钩,同样尺寸的视图,分辨率越高清晰度越好。另外还要引出一个重要的概念:PPI(pixelsperinch)PPI是图像分辨率的单位,图像PPI值越高,画面的细节就越丰富,因为单位面积的像素数量越多,一般PPI>300人眼难以分辨出来。分辨率分为水平和垂直两种,

    2022年4月17日
    146
  • 删除链表倒数第n个节点_求链表的倒数第m个元素

    删除链表倒数第n个节点_求链表的倒数第m个元素原题链接给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。进阶:你能尝试使用一趟扫描实现吗?示例 1:输入:head = [1,2,3,4,5], n = 2输出:[1,2,3,5]示例 2:输入:head = [1], n = 1输出:[]示例 3:输入:head = [1,2], n = 1输出:[1]提示:链表中结点的数目为 sz1 <= sz <= 300 <= Node.val <= 1001 <= n <= s

    2022年8月9日
    3
  • 数组长度计算_c语言计算数组长度的函数

    数组长度计算_c语言计算数组长度的函数(1)sizeof方法:sizeof(数组名)/sizeof(数组类型名)说明:数组占用字节除以数组类型所占字节,结果为数组元素个数(2)strlen说明:strlen,求字符串有效长度方法

    2022年8月5日
    2
  • Python表白代码合集:5种表白代码,找不到对象你来找我,这也太秀了叭

    Python表白代码合集:5种表白代码,找不到对象你来找我,这也太秀了叭文章目录一、容我啰嗦两句二、来吧,代码展示1、给女神比个小心心2、无限弹窗式表白3、这货不是表白代码,悄悄送给你们4、520表白墙5、抖音热门表白小软件6、无套路表白三、写在最后一、容我啰嗦两句爬虫看多了,对身体不好,我们来点现实的,学学表白找个女朋友他不香吗,对吧~文章最后教你们怎么打包成exe,如果你懒得搞懂代码怎么回事,直接复制代码打包成exe运行就好了。这样不管你发给别人也好,以后方便直接用也好,都很方便。咱就不整什么鸡皮疙瘩掉一地的情话啥的了,有需要的自行百度。二、来吧,代码展示

    2022年6月2日
    102

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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