二叉树的几种后序遍历方法,递归,非递归

本文深入探讨了二叉树的后序遍历算法,包括递归和非递归实现方法,如栈加集合及栈加prior的迭代算法,并讨论了后序遍历在求树深度、路径寻找等应用中的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二叉树的后序遍历算法比较难,尤其是非递归。但是后序遍历的应用最广泛,最容易出现算法题,因此一定要牢牢掌握。其中迭代的方法又较递归的方法快,但是也是非常令人头疼的一件事。

1.递归算法

public void postOrderRecur(TreeNode root){
    if(root != null){
        postOrderRecur(root.left);
        postOrderRecur(root.right);
        visit(root);
    }
}

递归算法比较简单,一般没有特殊要求的时候,首选递归算法。

2.非递归算法

我们知道,递归算法其实就是调用了系统栈,因此我们可以通过自己使用一个辅助栈将递归算法改为迭代的。

2.1 栈+集合

使用栈将递归转非递归,使用集合在常数时间内可以判断一个结点的孩子是否被访问过:

public void postOrder_(){
    if(root == null) return;
    Stack<TreeNode> s = new Stack<>();
    Set<TreeNode> visit = new HashSet<>();
    TreeNode p = root;
    s.push(p);
    while(!s.isEmpty()){
        p = s.peek();
        if(p.left != null && !visit.contains(p.left)){//左孩子存在且尚未被访问过
            s.push(p.left);
        }else if(p.right != null && !visit.contains(p.right)){//右孩子存在且未被访问过
            s.push(p.right);
        }else{
            p = s.pop();
            visited(p);//访问节点
            visit.add(p);
        }
    }
    return;
}

在这里插入图片描述

2.2 栈+prior

prior用于记录上一个被访问过的结点。如果一个结点的右孩子为空,或者右孩子已经被访问过了,则该节点可以被访问。判断右孩子为空不难,但是怎么判断右孩子已经被访问过了呢,当然我们可以像2.1中那样,额外使用一个集合,但是这里我们利用一条性质(书上没讲,别的地方也不会讲,其实也不算性质,就是在做题的时候发现的一条规律):在后序遍历序列中,一个结点的右孩子(如果存在)一定是紧挨着该节点并在该节点前面的。例如上图中的8前面是它的右孩子10,2前面是5,3前面是7…
那么我们想想,是不是当我们的上一个访问结点prior的值是某个结点p的右孩子的时候,那么这次一定轮到这个p结点被访问;
又因为我们知道,根据后序便利的规则,对于一个非空节点,每次我们都是先直接找到它的最左下子孙结点开始第一次访问的。例如图中的1,我们一直找到最左下结点8,才开始第一次访问,路径上的结点都暂存在栈中,然后回退的时候,才从栈中取出他们进行访问。也就是说,当从栈中取出一个结点的时候,它的左孩子已经必然被访问过了,因此我们这个时候只需要考虑它的右孩子。
而它的右孩子只有三种情况:

  1. 右孩子不存在,那么我们就可以直接访问该节点了;
  2. 右孩子存在但没被访问过,怎么判断呢,就是上面说的,右孩子不等于prior,那么我们开始转到右孩子那里去处理;
  3. 右孩子已经被访问过,前面说了,如果右孩子被访问了,那么接下来要被访问的必然是它自己,那么必定有右孩子==prior成立,此时我们可以访问该节点了。
public void postOrder(){
    if(root == null) return;
    Stack<TreeNode> s = new Stack<>();
    TreeNode p = root, prior = null;
    while (p != null || !s.isEmpty()){//p不为空或者栈不空
        while (p != null){//找到一个子树的最左下节点
            s.push(p.left);
            p = p.left;
        }
        p = s.peek();//从栈中取出的这个结点,其左孩子必定已经被访问过
        if (p.right != null && p.right != prior) {//如果该节点的右子树不空,且没有被访问过,转向右子树
            p = p.right;
            s.push(p);
            p = p.left;
        } else {//访问该节点
            p = s.pop();
            visit(p);
            prior = p;//标识访问过的节点,对于一个节点来说,他在后序遍历中的顺序是紧跟着其右孩子的(右孩子不空时)
            p = null;//访问完,将其指向空,因为对于栈顶节点(该节点的父亲),其左孩子已经被访问过了。
        }
    }
}

3.二叉树后序遍历的应用

二叉树的后序遍历有一个性值:每当访问一个节点时,栈中的结点正好全是它的祖先结点。我们可以利用这个性质求树的深度,求从根节点到某节点的路径,求两个结点的公共祖先等等,力扣上有很多这样的题目。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值