145. 二叉树的后序遍历
1.题目介绍
给定一个二叉树的根节点 root ,返回 它的 后序 遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[3,2,1]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
提示:
树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100
2.题解
2.1 递归
首先我们需要了解什么是二叉树的前序遍历:按照访问左子树——右子树——根节点的方式遍历这棵树,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。递归终止的条件为碰到空节点。
代码
//
// Created by trmbh on 2023-10-26.
// 145. 二叉树的后序遍历
#include<iostream>
#include<vector>
#include<string>
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) {
using namespace std;
if (root == nullptr) return arr;
else {
postorderTraversal(root->left);
postorderTraversal(root->right);
arr.push_back(root->val);
}
return arr;
}
private:
std::vector<int> arr;
};
int main(){
Solution solution;
TreeNode n1(3);
TreeNode n2(2, &n1, nullptr);
TreeNode n(1, nullptr,&n2);
std::vector<int> arr = solution.inorderTraversal(&n);
for (int num:arr){
std::cout << num << ' ';
}
}
2.2迭代
思路
在递归中,我们其实隐式地使用了栈来保存前面节点的信息;所以这里如果我们要使用迭代的方法的话,就应该使用显式栈来保存节点信息,并在返回时提供节点信息。
迭代和递归转化的实质就是,入栈替代了对于函数的递归调用,出栈+一些处理替代了return语句。
后序遍历的迭代算法比较复杂,实际上是一种(向左子树遍历到空节点-> 回退到根节点(过度) -> 遍历右子树 -> 回退到根节点(标记,出栈,入数组)),
这里需要补充一个pre指针指向上一个刚遍历过的节点,防止重复遍历的发生。
首先是对于左子树的遍历,这里有两种情况,
- 一种是尚未遍历过左子树的节点,那么遍历左子树直到节点为空为止;
- 一种是该节点为上一次已经遍历过左右子树的节点,这种便跳过左子树遍历,继续回转到栈中存储的上一个节点。
之后是对于右子树的遍历,先利用栈中存储的信息,回退到根节点,之后取root->right即可.
这里的root->right实际上又成为了新的根节点,再按照之前的方式遍历即可。
最后是对于根节点的输出,根节点必须满足自己的左右子树均被遍历过才可进行输出。
此时有两种情况,
- 该根节点的右子树为空
- 右子树的根节点刚刚遍历过(被标记,prev)
此时我们便可以进行(标记,出栈,入数组)的一系列操作了。
整个大循环的结束时(栈空 + root == pre)由于栈空代表目前节点正是最初二叉树的根节点,
而root == pre代表该节点的左右子树均遍历完毕,也就是整个二叉树后序遍历完毕!
举个简单的例子如下图所示
代码
class Solution {
public:
std::vector<int> postorderTraversal(TreeNode *root) {
std::stack<TreeNode *> stk;
std::vector<int> arr;
TreeNode *pre = nullptr; //存储上一个刚遍历过的节点
if (root == nullptr) {
return arr;
}
while (!stk.empty() || root != pre) {
// 遍历左子树(root != pre, 避免左子树重复遍历)
while (root != nullptr && root != pre) {
stk.push(root);
root = root->left;
}
// 访问上一个节点
root = stk.top();
// 遍历右子树(这里有一点递归的思想,将右子树看做一个新的二叉树)
root = root->right;
/* 上个节点左右子树均遍历完毕 */
if (root == nullptr || root == pre) {
root = stk.top(); //访问上个节点
stk.pop(); //顺序出栈, 回退到上个节点
arr.push_back(root->val); //输出
pre = root;//保存该节点,作为后续判断依据
}
}
return arr;
}
};
2.3 力扣题解
class Solution {
public:
std::vector<int> postorderTraversal(TreeNode *root) {
std::vector<int> res;
if (root == nullptr) {
return res;
}
std::stack<TreeNode *> stk;
TreeNode *prev = nullptr;
// stk.empty()代表当前根节点已经遍历,
// root == nullptr代表当前节点(根节点)的右子树遍历完毕,自身也遍历完毕
while (root != nullptr || !stk.empty()) {
//遍历左子树,入栈
while (root != nullptr) {
stk.emplace(root);
root = root->left;
}
//回退,出栈
root = stk.top();
stk.pop();
// 如果右子树为空 | 右子树的根节点为遍历过的节点(标记过),代表右子树已经遍历完
// prev标记该节点,root == nullptr防止重复遍历左子树(不走第一步while循环),之后回退,出栈返回上一个节点
if (root->right == nullptr || root->right == prev) {
res.emplace_back(root->val);
prev = root;
root = nullptr;
}
//该节点尚未遍历右子树,重新入栈,先遍历右子树
else {
stk.emplace(root);
root = root->right;
}
}
return res;
}
};