Code前端首页关于Code前端联系我们

二叉树的迭代遍历,递归能做到的,栈也能!附上java和PYTHON实现代码

terry 2年前 (2023-09-27) 阅读数 77 #数据结构与算法

文章来自代码随机笔记,作者程序员卡尔

二叉树的迭代遍历

读完这篇文章后,你可以用迭代的方法解决以下三个leet问题代码问题: ?那么顺序遍历呢?

我们在栈和队列中提到:匹配问题是栈的强项。 递归的实现是:每次递归调用都会将函数的局部变量、参数值和返回地址压入调用栈。 并且当递归返回时,上一次递归的参数会从栈顶弹出,所以这就是递归可以返回到上一层的原因。

至此大家应该都知道了,我们也可以使用栈来实现二叉树的前后中序遍历。

预购审核(迭代法)

首先我们来看看预购审核。

预订查看位于中间和左侧。每次先处理中间节点时,先将后面的节点放入堆栈,然后将右子节点添加到堆栈中,然后添加左子节点。

为什么要先添加右孩子,再添加左孩子?因为这是出栈时的中、左、右顺序。

动画如下: 二叉树的迭代遍历,递归能做的,栈也能做!附java、PYTHON实现代码

写出下面的代码并不难:(注意代码中的空节点并没有入栈)A这时候你会发现用迭代的方法来写前序遍历看似不难,其实也不难。

此时,您想更改预订评论的顺序并按顺序创建评论吗?

其实,真的不行!

但是当你使用按顺序写遍历的迭代方法时,你会发现套路不一样了。当前前序遍历的逻辑不能直接应用于中序遍历。

中序遍历(迭代法)

为了解释清楚,我先解释一下,现在迭代过程中,我们实际上有两个操作:

  1. 处理:将元素放入结果数组中
  2. 访问:节点的遍历

分析一下为什么刚才写的前序遍历代码不能和中序遍历共用,因为前序遍历的顺序是围绕中间的,先访问的元素是中间节点,要处理的元素也在中间节点,所以我现在可以写出一些相当简洁的代码,因为要访问的元素和要处理的元素是相同的顺序,而且都是中间节点。

然后按顺序看攻略。复习的顺序是左、中、右。首先访问二叉树顶部的节点,然后逐层访问,直到到达树左侧的底部,然后开始处理节点(即将节点的值放入到结果数组中),导致处理顺序和访问顺序不一致。

所以使用迭代方式按顺序写遍历时,是用指针遍历来帮助访问节点,而栈则是用来处理节点上的元素。

动画如下: 二叉树的迭代遍历,递归能做的,栈也能做!附java、PYTHON实现代码

中序遍历可以写如下代码:

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) { // 指针来访问节点,访问到最底层
                st.push(cur); // 将访问的节点放进栈
                cur = cur->left;                // 左
            } else {
                cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                st.pop();
                result.push_back(cur->val);     // 中
                cur = cur->right;               // 右
            }
        }
        return result;
    }
};

后序遍历(迭代法)

我们再看一下后序遍历。售前审核是左右,售后审核是左右。 ,那么我们只需要将前序遍历的代码顺序调整为center、right、left的遍历顺序,然后将结果数组反转,输出结果顺序为left、center,如下图:二叉树的迭代遍历,递归能做的,栈也能做!附java、PYTHON实现代码

所以只有邮购-审核需要预购审核的代码可能会略有变化。代码如下:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            result.push_back(node->val);
            if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
            if (node->right) st.push(node->right); // 空节点不入栈
        }
        reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
        return result;
    }
};

总结

目前我们采用迭代的方式,按顺序写出二叉树的前向和后向遍历。可以看到,预购和中购是完全不同的。这种编码风格不像递归编写,可以稍微调整代码来实现从前到后的顺序。

这是因为在前序遍历中访问节点(吞吐量节点)和处理节点(向结果数组中插入元素)可以同步处理,但顺序遍历无法同步!

上面这句话可能有同学不明白。我建议你自己用迭代的方法,先写引言,然后尝试看看你能不能写出序言,然后你就能理解了。

那么问题又来了,实现二叉树前后中序遍历的迭代方法是不是可以有统一的风格(即前序遍历可以通过以下方式实现中序和后序)更改代码顺序)?

这样的写法当然不容易理解。我们将在下一篇文章中重点讲解,敬请期待!

其他语言版本

Java:

// 前序遍历顺序:中-左-右,入栈顺序:中-右-左
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode node = stack.pop();
            result.add(node.val);
            if (node.right != null){
                stack.push(node.right);
            }
            if (node.left != null){
                stack.push(node.left);
            }
        }
        return result;
    }
}

// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur != null || !stack.isEmpty()){
           if (cur != null){
               stack.push(cur);
               cur = cur.left;
           }else{
               cur = stack.pop();
               result.add(cur.val);
               cur = cur.right;
           }
        }
        return result;
    }
}

// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode node = stack.pop();
            result.add(node.val);
            if (node.left != null){
                stack.push(node.left);
            }
            if (node.right != null){
                stack.push(node.right);
            }
        }
        Collections.reverse(result);
        return result;
    }
}

Python:

# 前序遍历-迭代-LC144_二叉树的前序遍历
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        # 根结点为空则返回空列表
        if not root:
            return []
        stack = [root]
        result = []
        while stack:
            node = stack.pop()
            # 中结点先处理
            result.append(node.val)
            # 右孩子先入栈
            if node.right:
                stack.append(node.right)
            # 左孩子后入栈
            if node.left:
                stack.append(node.left)
        return result
        
# 中序遍历-迭代-LC94_二叉树的中序遍历
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        stack = []  # 不能提前将root结点加入stack中
        result = []
        cur = root
        while cur or stack:
            # 先迭代访问最底层的左子树结点
            if cur:     
                stack.append(cur)
                cur = cur.left  
            # 到达最左结点后处理栈顶结点    
            else:  
                cur = stack.pop()
                result.append(cur.val)
                # 取栈顶元素右结点
                cur = cur.right 
        return result
        
# 后序遍历-迭代-LC145_二叉树的后序遍历
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        stack = [root]
        result = []
        while stack:
            node = stack.pop()
            # 中结点先处理
            result.append(node.val)
            # 左孩子先入栈
            if node.left:
                stack.append(node.left)
            # 右孩子后入栈
            if node.right:
                stack.append(node.right)
        # 将最终的数组翻转
        return result[::-1]

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门