先序遍历二叉树

1. 定义二叉树节点结构

#include <iostream>
#include <vector>
#include <stack>
#include <utility>

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) {}
};

2. 递归法

class Solution {
public:
    // 递归先序遍历
    std::vector<int> preorderRecursive(TreeNode* root) {
        std::vector<int> result;
        dfs(root, result);
        return result;
    }
    
private:
    void dfs(TreeNode* node, std::vector<int>& result) {
        if (!node) return;
        
        result.push_back(node->val);   // 访问根节点
        dfs(node->left, result);        // 遍历左子树
        dfs(node->right, result);       // 遍历右子树
    }
};

3. 迭代法(单栈法)

class Solution {
public:
    // 迭代先序遍历 - 使用栈
    std::vector<int> preorderIterative(TreeNode* root) {
        std::vector<int> result;
        if (!root) return result;
        
        std::stack<TreeNode*> stack;
        stack.push(root);
        
        while (!stack.empty()) {
            TreeNode* node = stack.top();
            stack.pop();
            
            result.push_back(node->val);  // 访问根节点
            
            // 注意:先右后左,因为栈是LIFO
            if (node->right) {
                stack.push(node->right);   // 右子节点先入栈(后访问)
            }
            if (node->left) {
                stack.push(node->left);    // 左子节点后入栈(先访问)
            }
        }
        
        return result;
    }
};

4. 迭代法(模拟递归过程)

class Solution {
public:
    // 模拟递归调用栈的迭代写法
    std::vector<int> preorderIterative2(TreeNode* root) {
        std::vector<int> result;
        if (!root) return result;
        
        // pair<节点, 是否已访问>
        std::stack<std::pair<TreeNode*, bool>> stack;
        stack.push({root, false});
        
        while (!stack.empty()) {
            auto [node, visited] = stack.top();
            stack.pop();
            
            if (!node) continue;
            
            if (visited) {
                result.push_back(node->val);  // 第二次遇到时访问
            } else {
                // 逆序入栈:右、左、根
                stack.push({node->right, false});
                stack.push({node->left, false});
                stack.push({node, true});      // 标记为已访问
            }
        }
        
        return result;
    }
};

5. 迭代法(手动模拟递归的另一种写法)

class Solution {
public:
    // 更接近递归思维的迭代写法
    std::vector<int> preorderIterative3(TreeNode* root) {
        std::vector<int> result;
        std::stack<TreeNode*> stack;
        TreeNode* current = root;
        
        while (current || !stack.empty()) {
            // 访问左子树直到最左边
            while (current) {
                result.push_back(current->val);  // 访问当前节点
                stack.push(current);              // 保存节点以便返回到右子树
                current = current->left;          // 移动到左子节点
            }
            
            // 回到父节点,转向右子树
            current = stack.top();
            stack.pop();
            current = current->right;
        }
        
        return result;
    }
};

复杂度分析

方法时间复杂度空间复杂度
递归O(n)O(h),h为树高
迭代(标准)O(n)O(h)
迭代(模拟递归)O(n)O(h)
迭代(手动模拟)O(n)O(h)

要点说明

  1. 标准迭代法:最简单直观,利用栈的LIFO特性

  2. 模拟递归法:使用pair标记节点状态,代码模式通用

  3. 手动模拟法:更接近递归的执行过程,适合理解递归本质

  4. 栈溢出考虑:对于深度很大的树,递归可能导致栈溢出,应优先使用迭代法

中序遍历二叉树

1. 递归法

#include <iostream>
#include <vector>
#include <stack>

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:
    // 递归中序遍历
    std::vector<int> inorderRecursive(TreeNode* root) {
        std::vector<int> result;
        dfs(root, result);
        return result;
    }
    
private:
    void dfs(TreeNode* node, std::vector<int>& result) {
        if (!node) return;
        
        dfs(node->left, result);    // 遍历左子树
        result.push_back(node->val); // 访问根节点
        dfs(node->right, result);   // 遍历右子树
    }
};

2. 迭代法(单栈法)

class Solution {
public:
    // 迭代中序遍历 - 使用栈
    std::vector<int> inorderIterative(TreeNode* root) {
        std::vector<int> result;
        std::stack<TreeNode*> stack;
        TreeNode* current = root;
        
        while (current || !stack.empty()) {
            // 一直向左走到最左边
            while (current) {
                stack.push(current);
                current = current->left;
            }
            
            // 访问最左边的节点
            current = stack.top();
            stack.pop();
            result.push_back(current->val);
            
            // 转向右子树
            current = current->right;
        }
        
        return result;
    }
};

3. 迭代法(模拟递归过程)

class Solution {
public:
    // 模拟递归调用栈的迭代写法
    std::vector<int> inorderIterative2(TreeNode* root) {
        std::vector<int> result;
        if (!root) return result;
        
        // pair<节点, 是否已访问>
        std::stack<std::pair<TreeNode*, bool>> stack;
        stack.push({root, false});
        
        while (!stack.empty()) {
            auto [node, visited] = stack.top();
            stack.pop();
            
            if (!node) continue;
            
            if (visited) {
                result.push_back(node->val);  // 第二次遇到时访问
            } else {
                // 逆序入栈:右、根、左
                stack.push({node->right, false});
                stack.push({node, true});      // 标记为已访问
                stack.push({node->left, false});
            }
        }
        
        return result;
    }
};

4. 迭代法(颜色标记法-通用模板)

class Solution {
public:
    // 颜色标记法:0表示未访问,1表示已访问
    std::vector<int> inorderIterative3(TreeNode* root) {
        std::vector<int> result;
        if (!root) return result;
        
        std::stack<std::pair<TreeNode*, int>> stack;
        stack.push({root, 0});
        
        while (!stack.empty()) {
            auto [node, color] = stack.top();
            stack.pop();
            
            if (!node) continue;
            
            if (color == 0) {
                // 未访问:按逆序入栈(右、根、左)
                stack.push({node->right, 0});
                stack.push({node, 1});      // 标记为已访问
                stack.push({node->left, 0});
            } else {
                result.push_back(node->val);  // 已访问,输出
            }
        }
        
        return result;
    }
};

5. Morris遍历(空间复杂度O(1))

class Solution {
public:
    // Morris中序遍历 - 空间复杂度O(1)
    std::vector<int> inorderMorris(TreeNode* root) {
        std::vector<int> result;
        TreeNode* current = root;
        
        while (current) {
            if (!current->left) {
                // 没有左子树,直接访问当前节点
                result.push_back(current->val);
                current = current->right;
            } else {
                // 找到左子树的最右节点(前驱节点)
                TreeNode* predecessor = current->left;
                while (predecessor->right && predecessor->right != current) {
                    predecessor = predecessor->right;
                }
                
                if (!predecessor->right) {
                    // 建立线索链接
                    predecessor->right = current;
                    current = current->left;
                } else {
                    // 已经访问过左子树,断开线索
                    predecessor->right = nullptr;
                    result.push_back(current->val);
                    current = current->right;
                }
            }
        }
        
        return result;
    }
};

复杂度分析

方法时间复杂度空间复杂度优点缺点
递归O(n)O(h)代码简洁可能栈溢出
标准迭代O(n)O(h)安全可控需要手动维护栈
模拟递归O(n)O(h)通用模板稍复杂
颜色标记O(n)O(h)易于理解额外状态标记
MorrisO(n)O(1)空间最优会修改树结构

关键要点

  1. 中序遍历特点:访问顺序是左子树 → 根节点 → 右子树

  2. 标准迭代法:核心是"一直向左走",使用栈保存路径

  3. 与先序遍历的区别:

    • 先序遍历:访问节点后再压栈

    • 中序遍历:节点出栈时才访问

  4. Morris遍历:利用线索二叉树思想,空间复杂度达到O(1),但会临时修改树结构

  5. 选择建议:日常用递归,深度大时用标准迭代,追求极致空间用Morris

后序遍历(左-右-根)的递归与迭代实现

1. 递归法

#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>

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:
    // 递归后序遍历
    std::vector<int> postorderRecursive(TreeNode* root) {
        std::vector<int> result;
        dfs(root, result);
        return result;
    }
    
private:
    void dfs(TreeNode* node, std::vector<int>& result) {
        if (!node) return;
        
        dfs(node->left, result);    // 遍历左子树
        dfs(node->right, result);   // 遍历右子树
        result.push_back(node->val); // 访问根节点
    }
};

2. 迭代法(双栈法)

class Solution {
public:
    // 后序遍历迭代法 - 双栈法
    std::vector<int> postorderIterativeDoubleStack(TreeNode* root) {
        std::vector<int> result;
        if (!root) return result;
        
        std::stack<TreeNode*> stack1, stack2;
        stack1.push(root);
        
        // 第一个栈处理节点顺序:根-右-左
        while (!stack1.empty()) {
            TreeNode* node = stack1.top();
            stack1.pop();
            stack2.push(node);  // 将节点压入第二个栈
            
            // 注意:先左后右,因为第二个栈会反转顺序
            if (node->left) stack1.push(node->left);
            if (node->right) stack1.push(node->right);
        }
        
        // 第二个栈弹出顺序:左-右-根
        while (!stack2.empty()) {
            result.push_back(stack2.top()->val);
            stack2.pop();
        }
        
        return result;
    }
};

3. 迭代法(单栈法-需要记录访问状态)

class Solution {
public:
    // 后序遍历迭代法 - 单栈法(需要记录上次访问的节点)
    std::vector<int> postorderIterativeSingleStack(TreeNode* root) {
        std::vector<int> result;
        if (!root) return result;
        
        std::stack<TreeNode*> stack;
        TreeNode* current = root;
        TreeNode* lastVisited = nullptr;
        
        while (current || !stack.empty()) {
            // 一直向左走
            while (current) {
                stack.push(current);
                current = current->left;
            }
            
            // 查看栈顶节点
            current = stack.top();
            
            // 如果右子树为空或已经访问过,则访问当前节点
            if (!current->right || current->right == lastVisited) {
                result.push_back(current->val);
                stack.pop();
                lastVisited = current;
                current = nullptr;  // 重要:避免重复遍历
            } else {
                // 否则转向右子树
                current = current->right;
            }
        }
        
        return result;
    }
};

4. 迭代法(模拟递归过程)

class Solution {
public:
    // 模拟递归调用栈的迭代写法
    std::vector<int> postorderIterativeSimulate(TreeNode* root) {
        std::vector<int> result;
        if (!root) return result;
        
        // pair<节点, 是否已访问>
        std::stack<std::pair<TreeNode*, bool>> stack;
        stack.push({root, false});
        
        while (!stack.empty()) {
            auto [node, visited] = stack.top();
            stack.pop();
            
            if (!node) continue;
            
            if (visited) {
                result.push_back(node->val);  // 第二次遇到时访问
            } else {
                // 逆序入栈:根、右、左(因为栈是LIFO)
                stack.push({node, true});      // 标记为已访问
                stack.push({node->right, false});
                stack.push({node->left, false});
            }
        }
        
        return result;
    }
};

5. 迭代法(颜色标记法)

class Solution {
public:
    // 颜色标记法:0表示未访问,1表示已访问
    std::vector<int> postorderIterativeColor(TreeNode* root) {
        std::vector<int> result;
        if (!root) return result;
        
        std::stack<std::pair<TreeNode*, int>> stack;
        stack.push({root, 0});
        
        while (!stack.empty()) {
            auto [node, color] = stack.top();
            stack.pop();
            
            if (!node) continue;
            
            if (color == 0) {
                // 未访问:按逆序入栈(根、右、左)
                stack.push({node, 1});      // 标记为已访问
                stack.push({node->right, 0});
                stack.push({node->left, 0});
            } else {
                result.push_back(node->val);  // 已访问,输出
            }
        }
        
        return result;
    }
};

6. Morris后序遍历(空间复杂度O(1))

class Solution {
public:
    // Morris后序遍历 - 需要反转输出
    std::vector<int> postorderMorris(TreeNode* root) {
        std::vector<int> result;
        TreeNode dummy(0);
        dummy.left = root;
        TreeNode* current = &dummy;
        
        while (current) {
            if (!current->left) {
                current = current->right;
            } else {
                // 找到左子树的最右节点(前驱节点)
                TreeNode* predecessor = current->left;
                while (predecessor->right && predecessor->right != current) {
                    predecessor = predecessor->right;
                }
                
                if (!predecessor->right) {
                    // 建立线索链接
                    predecessor->right = current;
                    current = current->left;
                } else {
                    // 已经访问过左子树,断开线索并输出左子树路径
                    predecessor->right = nullptr;
                    printReversePath(current->left, result);
                    current = current->right;
                }
            }
        }
        
        return result;
    }
    
private:
    // 反转输出从node到其最右节点的路径
    void printReversePath(TreeNode* node, std::vector<int>& result) {
        int count = 0;
        while (node) {
            result.push_back(node->val);
            node = node->right;
            count++;
        }
        // 反转这部分结果
        std::reverse(result.end() - count, result.end());
    }
};

关键要点

  1. 后序遍历特点:访问顺序是左子树 → 右子树 → 根节点

  2. 最难实现:因为根节点最后访问,无法像前序、中序那样简单处理

  3. 双栈法:最直观,利用"根-右-左"反转得到"左-右-根"

  4. 单栈法:需要记录上次访问的节点来判断右子树是否已访问

  5. 通用模板:使用pair标记访问状态,适用于所有遍历方式

  6. 选择建议:

    • 日常开发:递归法

    • 生产环境(树很深):双栈法(简单可靠)

    • 追求空间效率:单栈法

    • 面试准备:掌握双栈法和单栈法

三种遍历方式对比

遍历方式递归代码顺序迭代难点常用迭代方法
先序遍历根-左-右简单单栈法
中序遍历左-根-右中等单栈法
后序遍历左-右-根较难双栈法/标记法

各方法复杂度对比

方法时间复杂度空间复杂度优点缺点
递归O(n)O(h)代码最简洁可能栈溢出
双栈法O(n)O(n)直观易懂需要两个栈
单栈法O(n)O(h)空间较优逻辑稍复杂
模拟递归O(n)O(h)通用模板需要状态标记
颜色标记O(n)O(h)易于理解额外状态
MorrisO(n)O(1)空间最优实现复杂
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐