二叉树遍历之统一迭代法:轻松实现前中后序遍历

二叉树遍历之统一迭代法:轻松实现前中后序遍历

在二叉树的遍历中,我们常常会遇到前序遍历、中序遍历和后序遍历这三种方式。递归是一种很直观的实现方法,但迭代法也有其独特的优势。今天我们要介绍一种二叉树遍历的统一迭代法,它可以像递归法通过调整顺序那样,轻松实现前、中、后三种遍历方式,是不是很神奇呢?下面就来详细看看吧。

二叉树遍历基础

(一)前序遍历(Pre - order Traversal)

顺序是根节点、左子树、右子树。对于给定的二叉树,先访问根节点,然后递归地遍历左子树,最后递归地遍历右子树。例如,对于二叉树:

       1
     /   \
    2     3
   / \   / \
  4   5 6   7

前序遍历的结果是 1 2 4 5 3 6 7

(二)中序遍历(In - order Traversal)

顺序是左子树、根节点、右子树。先递归遍历左子树,然后访问根节点,最后递归遍历右子树。上述二叉树的中序遍历结果是 4 2 5 1 6 3 7

(三)后序遍历(Post - order Traversal)

顺序是左子树、右子树、根节点。先递归遍历左子树,然后递归遍历右子树,最后访问根节点。此二叉树的后序遍历结果是 4 5 2 6 7 3 1

统一迭代法原理

核心思想

统一迭代法的核心是利用栈来模拟递归的过程。我们知道,递归是通过函数调用栈来实现的,而这里我们手动用栈来控制节点的访问顺序。关键在于,对于不同的遍历顺序(前、中、后序),我们按照与遍历方向相反的顺序将节点压入栈中。

解题关键(中序为例)

1. 中序遍历的顺序特点

中序遍历二叉树的顺序是先访问左子树,然后访问根节点,最后访问右子树。这是整个解题思路围绕的核心规则,我们要通过迭代的方式模拟出这个顺序的访问过程。

2. 利用栈来辅助实现

栈是实现中序遍历统一迭代法的关键数据结构。它具有后进先出(LIFO)的特性,能够帮助我们按照特定的顺序处理二叉树的节点。

3. 入栈顺序与标记处理

  • 入栈方向与遍历方向相反:
    • 在中序遍历中,实际访问顺序是左子树、根节点、右子树,但我们入栈时要按照右子树、根节点、左子树的顺序进行。这是因为栈的后进先出特性,我们先把右子树压入栈,后续出栈时它就会在后面才被处理,从而能先处理左子树,符合中序遍历先左后右再根的顺序。
    • 例如,对于一个二叉树节点 node,当它不为空时,我们先将其右子节点(如果存在)通过 if (node->right) st.push(node->right); 压入栈。
  • 标记已处理节点:
    • 在把根节点压入栈后,我们紧接着压入一个特殊的标记 NULL,即 st.push(NULL);。这个标记的作用是用来标识当前根节点虽然已经被压入栈,但还没有真正完成中序遍历意义上的处理(也就是还没有将其值添加到结果集中)。
    • 然后再将左子节点(如果存在)通过 if (node->left) st.push(node->left); 压入栈。

4. 出栈与结果收集

  • 处理标记节点:
    • 在循环过程中,当栈顶元素为 NULL 时,这就表示遇到了之前压入的标记节点,意味着前面压入的那个根节点现在可以进行真正的中序遍历处理了。
    • 此时,我们先将这个 NULL 标记弹出栈,即 st.pop();
  • 获取并处理根节点:
    • 接着,再取出栈顶的节点(此时栈顶就是之前压入的那个根节点了),存到一个临时变量 node 中,然后再把这个节点从栈顶弹出,即通过 node = st.top(); st.pop(); 操作。
    • 最后,将这个根节点的 val 值通过 result.push_back(node->val); 添加到结果集 result 中,这样就完成了一个节点的中序遍历处理,将其值按照中序遍历的顺序放入了结果集中。

5. 整体循环控制

通过一个 while 循环来不断地处理栈中的节点,只要栈 st 不为空,循环就会持续进行,即 while (!st.empty())。在每次循环中,根据栈顶节点的情况(是普通节点还是标记节点)进行上述相应的操作,从而逐步完成整个二叉树的中序遍历,最终返回存储着中序遍历结果的 result 向量。

总的来说,中序遍历统一迭代法的关键思路就是利用栈的特性,通过巧妙地设置入栈顺序(与遍历顺序相反)并借助特殊标记来处理节点,按照中序遍历的规则准确地将节点值收集到结果集中,实现二叉树的中序遍历过程。

94. 二叉树的中序遍历

当节点不为空时,我们先弹出当前节点,然后将右子节点(如果存在)、当前节点、一个空标记(用于标记节点已经被处理过一次,但还没输出)、左子节点(如果存在)依次压入栈。当遇到空标记时,说明当前节点的左子树已经处理完,此时弹出栈顶节点(即当前节点)并将其值加入结果集。这样就实现了中序遍历,按照左 - 中 - 右的顺序处理节点。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;//存放最终返回的遍历结果
        stack<TreeNode*> st;//将待处理节点放入栈中存储
        if(root!=NULL) st.push(root);
        while(!st.empty()){
            TreeNode* node=st.top();
            if(node!=NULL){
                st.pop();
                //入栈方向和遍历方向相反,中序:左中右;入栈:右中左
                if(node->right) st.push(node->right);
                st.push(node);
                st.push(NULL);
                if(node->left) st.push(node->left);
            }else{
                st.pop();
                node=st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

144. 二叉树的前序遍历

对于前序遍历,当节点不为空时,先弹出当前节点,然后将右子节点、左子节点、当前节点、空标记依次压入栈。当遇到空标记时,弹出当前节点并加入结果集,这样就实现了先访问根节点,再访问左子树和右子树的前序遍历顺序(中 - 左 - 右)。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;//存放最终返回的遍历结果
        stack<TreeNode*> st;//将待处理节点放入栈中存储
        if(root!=NULL) st.push(root);
        while(!st.empty()){
            TreeNode* node=st.top();
            if(node!=NULL){
                st.pop();
                 //入栈方向和遍历方向相反,前序:中左右;入栈:右左中
                if(node->right) st.push(node->right);
                if(node->left) st.push(node->left);
                st.push(node);
                st.push(NULL);
            }else{
                st.pop();
                node=st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

145. 二叉树的后序遍历

在后序遍历中,当节点不为空时,先弹出当前节点,然后将当前节点、空标记、右子节点、左子节点依次压入栈。当遇到空标记时,弹出当前节点并加入结果集,从而实现了左 - 右 - 中的后序遍历顺序。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;//存放最终返回的遍历结果
        stack<TreeNode*> st;//将待处理节点放入栈中存储
        if(root!=NULL) st.push(root);
        while(!st.empty()){
            TreeNode* node=st.top();
            if(node!=NULL){
                st.pop();
                 //入栈方向和遍历方向相反,后序:左右中;入栈:中右左
                st.push(node);
                st.push(NULL);
                if(node->right) st.push(node->right);
                if(node->left) st.push(node->left);
            }else{
                st.pop();
                node=st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

总结

通过这种统一迭代法,我们巧妙地利用栈实现了二叉树的前、中、后序遍历。它的优点在于,我们不需要像递归那样依赖系统的函数调用栈,对于一些对栈空间有要求的场景可能更适用。而且这种统一的方法,让我们只需要记住一个基本的框架,通过调整入栈的顺序,就可以轻松实现不同的遍历方式,大大提高了代码的复用性和可维护性。希望大家通过本文对二叉树的遍历有更深入的理解和掌握。

参考资料
代码随想录

144. 二叉树的前序遍历 - 力扣(LeetCode)

94. 二叉树的中序遍历 - 力扣(LeetCode)

145. 二叉树的后序遍历 - 力扣(LeetCode)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值