算法day12|二叉树的前中后序遍历(递归写法、非递归写法)
二叉树的前中后序遍历(递归写法)
用递归的方式实现前序遍历:
class Solution {
public:
void travel(TreeNode* root,vector<int>& vec)
{
if(root==nullptr)
return;
vec.push_back(root->val);
travel(root->left,vec);
travel(root->right,vec);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
travel(root,result);
return result;
}
};
每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
以上3点都是卡哥总结的,这对自己再补充一条:确定好递归的函数!通过模拟过程和预测函数参数,来确定真正的递归函数(往往递归里面是有2个函数的,所以需要判断到底哪个里面有递归)
后序和中序同理:
145.二叉树的后序遍历
class Solution {
public:
void travel(TreeNode* root,vector<int>& result)
{
if(root==nullptr)
return;
travel(root->left,result);
travel(root->right,result);
result.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
travel(root,result);
return result;
}
};
94.中序遍历
class Solution {
public:
void travel(TreeNode* root,vector<int>&result)
{
if(root==nullptr)
return;
travel(root->left,result);
result.push_back(root->val);
travel(root->right,result);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
travel(root,result);
return result;
}
};
144.前序遍历(非递归写法)
我之前在王道学过,在看一遍视频之后就很自然地写出了:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
st.push(root);
while(!st.empty())
{
TreeNode*temp=st.top();
st.pop();
result.push_back(temp->val);
if(temp->right)
st.push(temp->right);
if(temp->left)
st.push(temp->left);
}
return result;
}
};
所谓的非递归写法,也就是直接利用递归的底层原理——栈,来解决谦虚遍历的问题
总体思路:根节点入栈,然后根节点出栈,出栈之后根的右孩子、左孩子立即依次进栈(先右后左)。以此迭代
代码细节:
-
栈的数据类型是TreeNode*,第一次见到
-
在入栈时注意要去除NULL的情况
145.后续遍历(非递归写法)
这题的处理非常巧妙,代码如下:
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
st.push(root);
while(!st.empty())
{
TreeNode*temp=st.top();
st.pop();
result.push_back(temp->val);
if(temp->right)
st.push(temp->left);
if(temp->left)
st.push(temp->right);
}
reverse(result.begin(),result.end());
return result;
}
};
本质上是在前序遍历的修改,将原来的根左右改成根右左,然后再反转,就成了左右根,太妙了
94.中序遍历(非递归写法)
上手难度还是很大的,具体代码如下:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode*cur=root;
while(cur!=nullptr||!st.empty())
{
if(cur!=nullptr)
{
st.push(cur);
cur=cur->left;
}
else
{
cur=st.top();
st.pop();
result.push_back(cur->val);
cur=cur->right;
}
}
return result;
}
};
总体逻辑:
-
首先明确中序的特点:访问(遍历)和处理(即记录在result数组中)是不同步的
-
对于访问:用栈来记录访问过的元素,核心逻辑是:对于一个结点,如果有左孩子,则将左孩子入栈(因为左孩子先出);如果没有左孩子,就出栈。出栈之后再立即判断它是否有右孩子:如果有,则将右孩子入栈;如果没有,那么就让栈中的下一个结点出栈。出栈之后再立即判断它是否有右孩子:如果有,则将右孩子入栈;如果没有,那么就让栈中的下一个结点出栈…以此迭代。
-
访问的方法:定义一个迭代器,通过该迭代器来获取结点。好处:非常自由,可以随意指向我们想要处理的元素,例如:cur=st.top();