二叉树非递归后序遍历的4种优雅解法

本文介绍了四种不同的后序遍历算法:状态机法、WorkingList法、输出头插法和输出序列逆序法,通过实例代码详细展示了每种方法的工作原理和实现。这些方法适用于解决树形数据结构的遍历问题,理解它们有助于提高算法设计和实现技巧。

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

方法1:状态机法

状态由两个部分组成

  1. 当前树的根节点root
  2. 辅助Stack的状态

算法就是根据当前的状态,不断跳转到下一个状态,直到root和Stack都为空。Stack里存的是当前树遍历之后再处理的节点,也就是当前树的父亲节点。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> output = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode pre = null;
        while (root != null || stack.size() > 0) {
            if (root != null) {
                stack.push(root);
                root = root.left;
            }
            else {
                root = stack.pop();
                if (root.right == null || pre == root.right) { // no right tree or has been handled
                    output.add(root.val);
                    pre = root;
                    root = null;
                }
                else { //has right child and has not been handled
                    stack.push(root);
                    root = root.right;
                }
            }
        }
        return output;
    }
}

方法2:Working List法

核心是一个Working list,算法不断从中取一个任务进行处理,过程中会产生新的子任务插入到Working list。任务分为两种:

  1. 复合任务:“后序遍历”以当前节点为根结点的树
  2. 打印任务:直接打印当前节点的value

对于复合任务的处理:

  1. 产生3个新的子任务,左右子树的后续遍历任务和当前节点的打印任务
  2. 新任务插入到队列的顺序很关键,要根据后序遍历的语义要求进行插入。因为这个任务队列是Stack,后处理的任务要先插。
class Solution(object):
    def postorderTraversal(self, root):
        result, stack = [], [(root, 1)]
        if root is None: return result
        while len(stack) > 0:
            node, taskType  = stack.pop()
            if taskType == 1: # 1 for traverse job, 插入三个新任务,注意顺序
                stack.append((node, 0)) # 0 for basic print job
                if node.right is not None: stack.append((node.right, 1))
                if node.left is not None: stack.append((node.left, 1))
            else: result.append(node.val)
        return result

方法3:输出头插法

后序遍历要求根节点最后输出,造成了“先访问到,但是不能输出,只能暂存后续再处理”的处理困境。换个思路,能否访问到就输出?答案是肯定的,只要保证先输出的在输出序列最后,类似链表的头插法,在头部插入,先插入的就在序列最后了。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        LinkedList<Integer> output = new LinkedList<>();
        if (root == null) {
            return output;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            root = stack.pop();
            output.addFirst(root.val);
            if (root.left != null) {
                stack.push(root.left);
            }
            if (root.right != null) {
                stack.push(root.right);
            }
        }
        return output;
    }
}

方法4: 输出序列逆序法

和方法3原理相同,只是不是通过头插进行逆序,而是先正常输出序列,最后再逆序的方式

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> output = new ArrayList<>();
        if (root == null) {
            return output;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            root = stack.pop();
            output.add(root.val);
            if (root.left != null) {
                stack.push(root.left);
            }
            if (root.right != null) {
                stack.push(root.right);
            }
        }

        Collections.reverse(output);
        return output;
    }
}
### 关于二叉树前序、中序和后序遍历的题目与解法 #### 一、基本概念 二叉树的三种主要遍历方式分别是前序遍历(Pre-order Traversal)、中序遍历(In-order Traversal)以及后序遍历(Post-order Traversal)。这三种遍历的核心在于访问根节点的时间不同。 - **前序遍历**:按照“根 -> 左子树 -> 右子树”的顺序进行访问。 - **中序遍历**:按照“左子树 -> 根 -> 右子树”的顺序进行访问。 - **后序遍历**:按照“左子树 -> 右子树 -> 根”的顺序进行访问。 这些遍历可以通过递归实现,也可以通过迭代配合栈来完成[^1]。 --- #### 二、典型题目解析 ##### 题目1:已知前序遍历和中序遍历序列,求后序遍历序列 给定一棵二叉树的前序遍历 `preorder` 和中序遍历 `inorder` 序列,要求返回该二叉树后序遍历序列。 ###### 解法: 可以利用分治的思想解决此问题。具体步骤如下: 1. 前序遍历的第一个元素即为当前子树的根节点。 2. 在中序遍历中找到该根节点的位置,则其左边部分表示左子树,右边部分表示右子树。 3. 对左右子树分别递归调用相同逻辑。 4. 将结果逆序即可得到最终的后序遍历序列。 以下是 Python 实现代码: ```python def buildTree(preorder, inorder): if not preorder or not inorder: return [] root_val = preorder[0] idx = inorder.index(root_val) left_inorder = inorder[:idx] right_inorder = inorder[idx + 1:] left_preorder = preorder[1:1+len(left_inorder)] right_preorder = preorder[1+len(left_inorder):] result = buildTree(left_preorder, left_inorder) + \ buildTree(right_preorder, right_inorder) + [root_val] return result ``` 这种方法基于递归构建子树并逐步拼接结果。 --- ##### 题目2:仅使用栈模拟二叉树的中序遍历 目标是在不使用递归的情况下,借助栈数据结构完成二叉树的中序遍历。 ###### 解法: 核心思想是先尽可能地向左深入,直到无法继续为止;随后回退至上一层级,并尝试访问右侧分支。 以下是 C++ 的实现代码: ```cpp vector<int> inorderTraversal(TreeNode* root) { vector<int> res; stack<TreeNode*> stk; while (root || !stk.empty()) { while (root) { stk.push(root); root = root->left; // 不断进入左子树 } root = stk.top(); stk.pop(); res.push_back(root->val); // 处理当前节点 root = root->right; // 转向右子树 } return res; } ``` 这段代码展示了如何通过显式的栈操作替代隐式函数调用堆栈[^3]。 --- ##### 题目3:验证某序列是否为合法的后序遍历 输入一个整数数组,判断它是否可能是某个二叉搜索树的后序遍历结果。 ###### 解法: 对于一颗 BST 来说,在后序遍历过程中最后一个数字必然是根节点。因此可以根据这一特性划分左右子树范围,并进一步校验各区间内部性质。 以下是 Java 版本解决方案: ```java public boolean verifyPostorder(int[] postorder) { return recur(postorder, 0, postorder.length - 1); } private boolean recur(int[] postorder, int i, int j){ if(i >= j) return true; int p = i; while(postorder[p] < postorder[j]) p++; int m = p; while(postorder[p] > postorder[j]) p++; return p == j && recur(postorder,i,m-1)&&recur(postorder,m,j-1); } ``` 这里采用了分而治之策略反复检验每一段区间的合法性[^4]。 --- ### 总结 以上介绍了几种常见的涉及二叉树遍历的相关算法及其应用实例。无论是采用何种形式表达——递归还是非递归版本——都需要深刻理解各自特点以便灵活运用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值