文章目录
前言
我们在做二叉树遍历oj时,题目要求用递归法、迭代法,但是我们实际做题时,总是会迷迷糊糊,卡子今天来总结下二叉树遍历的解题框架
一、递归法
- 明确递归三要素
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
- 时空复杂度
时间复杂度:o(n)
空间复杂度:o(log n)
1.1 前序遍历(带框架)
// 前序遍历
class Solution {
public:
// 确定递归的参数和返回值
void preOrder(TreeNode* root, vector<int>& res)
{
if (nullptr == root) // 终止条件
{
return;
}
// 单层递归的逻辑,根据遍历顺序确定
res.push_back(root->val);
preOrder(root->left, res);
preOrder(root->right, res);
}
vector<int> preorderTraversal(TreeNode* root)
{
vector<int> res;
preOrder(root, res);
return res;
}
};
1.2 后序遍历
// 后序遍历
class Solution {
public:
void postOrder(TreeNode* root, vector<int>& res)
{
if (nullptr == root)
{
return;
}
postOrder(root->left, res);
postOrder(root->right, res);
res.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode* root)
{
vector<int> res;
postOrder(root, res);
return res;
}
};
1.3 中序遍历
// 中序遍历
class Solution {
public:
void inOrder(TreeNode* root, vector<int>& res)
{
if (nullptr == root)
{
return;
}
inOrder(root->left, res);
res.push_back(root->val);
inOrder(root->right, res);
}
vector<int> inorderTraversal(TreeNode* root)
{
vector<int> res;
inOrder(root, res);
return res;
}
};
时空复杂度
时间复杂度:o(n)
空间复杂度:o(n)
二、迭代法-1(非统一写法)
- 迭代法原理:
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。迭代法就是借用栈来模拟递归的过程。
- 前后序
- 前序:要访问的元素和要处理的元素顺序是一致的,都是中间节点,所以每次访问的同时就处理元素
- 后序:将前序的操作顺序改变,最后逆置数组可实现
- 中序
- 中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的
- 需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素
2.1 前序遍历(带框架)
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root)
{
vector<int> res;
if (nullptr == root)
{
return res;
}
// 前序遍历(根左右),明确遍历顺序
stack<TreeNode*> st;
// 先将根节点提前入栈
st.push(root);
while (!st.empty())
{
// 取到当前节点
TreeNode* node = st.top();
// 前序遍历的待处理节点就是当前节点node
res.push_back(node->val);
st.pop();
// 将该节点以下的待访问节点入栈(不为空),先进后出的原则(入栈顺序:右左)
if (node->right) st.push(node->right);
if (node->left) st.push(node->left);
}
return res;
}
};
2.2 后序遍历
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root)
{
vector<int> res;
if (nullptr == root)
{
return res;
}
// 后序遍历(左右根)
// 前序遍历(根左右),逆置后:右左根,调整左右子树顺序,变为左右根,即后序遍历顺序
stack<TreeNode*> st;
st.push(root);
while (!st.empty())
{
TreeNode* node = st.top();
res.push_back(node->val);
st.pop();
// 调整左右子树入栈顺序
if (node->left) st.push(node->left);
if (node->right) st.push(node->right);
}
// 逆置数组res
reverse(res.begin(), res.end());
return res;
}
};
2.3 ※中序遍历(带框架)
// 中序遍历
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root)
{
vector<int> res;
if (nullptr == root)
{
return res;
}
stack<TreeNode*> st;
// 栈用来处理元素+记录访问过的元素
// st.push(root); // 无需提前入栈!!!~
// 需要借助cur指针来访问元素
TreeNode* cur = root;
// 指针和栈都为空时,结束循环
while (cur || !st.empty())
{
// 根据cur是否为空来确定访问元素还是处理元素
if (cur)
{
// cur不为空,继续访问元素(直到最左),同时将访问过的元素记录在栈
// 访问元素,一路向左,直到为空,访问路径存在栈中
st.push(cur);
cur = cur->left;
}
else
{
// cur为空
// 1-记录栈顶元素,处理栈顶元素(当前节点以下的最左不为空的元素)
cur = st.top(); // 直接用cur记录!!!~
res.push_back(cur->val);
st.pop();
// 访问右子树
cur = cur->right; // 判空交给while循环和if
}
}
return res;
}
};
二、迭代法-2(统一写法)
- 统一迭代法:将待访问和处理的节点都放在栈中,如果是待处理的节点就在其入栈后紧接着入栈一个空指针作为标记
vector<int> res;
if (nullptr == root)
{
return res;
}
stack<TreeNode*> st;
st.push(root);
while (!st.empty())
{
// 记录栈顶元素
TreeNode* node = st.top();
if (nullptr == node)
{
// 栈顶元素为空,说明遇到待处理节点
st.pop();
res.push_back(st.top());
st.pop();
}
else
{
// 栈顶元素不为空,说明遇到待访问节点
// 一、前序遍历(根左右,入栈顺序右左根)
// 1-先将该节点pop,防止重复操作,后面会重新调整顺序将该节点入栈
st.pop();
// 2-根据前中后序顺序,以及确定当前的待访问节点,来根据先进后出排序入栈
if (node->right) st.push(node->right);
if (node->left) st.push(node->left);
st.push(node); // 栈是先进后出,所以最后入前序当前节点
st.push(nullptr); // 中节点访问过,但是还没有处理,加入空节点做为标记
// 二、后序遍历(左右根,入栈顺序根右左)
// 1-先将该节点pop,防止重复操作,后面会重新调整顺序将该节点入栈
st.pop();
// 2-根据前中后序顺序,以及确定当前的待访问节点,来根据先进后出排序入栈
st.push(node);
st.push(nullptr); // 中节点访问过,但是还没有处理,加入空节点做为标记
if (node->right) st.push(node->right);
if (node->left) st.push(node->left);
// 三、中序遍历(左根右,入栈顺序右根左)
// 1-先将该节点pop,防止重复操作,后面会重新调整顺序将该节点入栈
st.pop();
// 2-根据前中后序顺序,以及确定当前的待访问节点,来根据先进后出排序入栈
if (node->right) st.push(node->right);
st.push(node);
st.push(nullptr); // 中节点访问过,但是还没有处理,加入空节点做为标记
if (node->left) st.push(node->left);
}
}
return res;
总结
这里对文章进行总结:
以上就是今天总结的内容,本文包括了我自己对二叉树遍历方法的小经验,分享给大家。
真💙欢迎各位给予我更好的建议,欢迎!小编创作不易,觉得有用可以一键三连哦,感谢大家。peace
希望大家一起坚持学习,共同进步。梦想一旦被付诸行动,就会变得神圣。
欢迎各位大佬批评建议,分享更好的方法!!!🙊🙊🙊